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 */ /* global $, APP, JitsiMeetJS, config, interfaceConfig */
import {openConnection} from './connection'; import {openConnection} from './connection';
//FIXME: import Invite from './modules/UI/invite/Invite';
import createRoomLocker from './modules/UI/authentication/RoomLocker'; import ContactList from './modules/UI/side_pannels/contactlist/ContactList';
//FIXME:
import AuthHandler from './modules/UI/authentication/AuthHandler'; import AuthHandler from './modules/UI/authentication/AuthHandler';
import ConnectionQuality from './modules/connectionquality/connectionquality'; import ConnectionQuality from './modules/connectionquality/connectionquality';
@ -26,7 +26,7 @@ const ConferenceErrors = JitsiMeetJS.errors.conference;
const TrackEvents = JitsiMeetJS.events.track; const TrackEvents = JitsiMeetJS.events.track;
const TrackErrors = JitsiMeetJS.errors.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. * Indicates whether the connection is interrupted or not.
@ -363,18 +363,7 @@ class ConferenceConnector {
switch (err) { switch (err) {
// room is locked by the password // room is locked by the password
case ConferenceErrors.PASSWORD_REQUIRED: case ConferenceErrors.PASSWORD_REQUIRED:
APP.UI.markRoomLocked(true); APP.UI.emitEvent(UIEvents.PASSWORD_REQUIRED);
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);
});
break; break;
case ConferenceErrors.CONNECTION_ERROR: case ConferenceErrors.CONNECTION_ERROR:
@ -403,8 +392,7 @@ class ConferenceConnector {
}, 5000); }, 5000);
// notify user that auth is required // notify user that auth is required
AuthHandler.requireAuth(room, this.invite.getRoomLocker().password);
AuthHandler.requireAuth(room, roomLocker.password);
break; break;
case ConferenceErrors.RESERVATION_ERROR: case ConferenceErrors.RESERVATION_ERROR:
@ -576,6 +564,8 @@ export default {
this.isDesktopSharingEnabled = this.isDesktopSharingEnabled =
JitsiMeetJS.isDesktopSharingEnabled(); JitsiMeetJS.isDesktopSharingEnabled();
APP.UI.ContactList = new ContactList(room);
// if user didn't give access to mic or camera or doesn't have // if user didn't give access to mic or camera or doesn't have
// them at all, we disable corresponding toolbar buttons // them at all, we disable corresponding toolbar buttons
if (!tracks.find((t) => t.isAudioTrack())) { if (!tracks.find((t) => t.isAudioTrack())) {
@ -888,7 +878,7 @@ export default {
room = connection.initJitsiConference(APP.conference.roomName, room = connection.initJitsiConference(APP.conference.roomName,
this._getConferenceOptions()); this._getConferenceOptions());
this._setLocalAudioVideoStreams(localTracks); this._setLocalAudioVideoStreams(localTracks);
roomLocker = createRoomLocker(room); this.invite = new Invite(room);
this._room = room; // FIXME do not use this this._room = room; // FIXME do not use this
let email = APP.settings.getEmail(); let email = APP.settings.getEmail();
@ -1316,13 +1306,6 @@ export default {
APP.UI.updateRecordingState(status); 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) { room.on(ConferenceEvents.USER_STATUS_CHANGED, function (id, status) {
APP.UI.updateUserStatus(id, status); APP.UI.updateUserStatus(id, status);
}); });
@ -1348,19 +1331,6 @@ export default {
"resizable,scrollbars=yes,status=1"); "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.AUDIO_MUTED, muteLocalAudio);
APP.UI.addListener(UIEvents.VIDEO_MUTED, muteLocalVideo); 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; overflow: hidden;
} }
p {
margin: 0;
}
html, body, input, textarea, keygen, select, button { html, body, input, textarea, keygen, select, button {
font-family: $baseFontFamily !important; font-family: $baseFontFamily !important;
} }
@ -26,16 +30,17 @@ html, body, input, textarea, keygen, select, button {
} }
input[type='text'], input[type='password'], textarea { input[type='text'], input[type='password'], textarea {
-webkit-user-select: text;
user-select: text;
display: inline-block; display: inline-block;
padding: 5px; width: 100%;
color: $defaultDarkColor; padding: 5px 7px;
color: $inputColor;
border-radius: $borderRadius; border-radius: $borderRadius;
line-height: 32px; line-height: 32px;
letter-spacing: $letterSpacing;
height: 32px; height: 32px;
text-align: left; text-align: left;
border:1px solid $inputBorderColor; border:1px solid $inputBorderColor;
background-color: $inputBackground;
outline: none; /* removes the default outline */ outline: none; /* removes the default outline */
resize: none; /* prevents the user-resizing, adjust to taste */ resize: none; /* prevents the user-resizing, adjust to taste */
} }
@ -43,7 +48,8 @@ input[type='text'], input[type='password'], textarea {
textarea { textarea {
overflow: hidden; overflow: hidden;
word-wrap: break-word; word-wrap: break-word;
resize: horizontal; resize: none;
line-height: 1.5em;
} }
button.no-icon { button.no-icon {
@ -53,12 +59,9 @@ button.no-icon {
button, input, select, textarea { button, input, select, textarea {
margin: 0; margin: 0;
vertical-align: baseline; vertical-align: baseline;
color: $defaultDarkColor; color: $inputColor;
background: $inputLightBackground; font-size: 1em;
font-size: 12px; letter-spacing: $letterSpacing;
border: none;
box-shadow: none;
outline: none;
} }
button, select, input[type="button"], button, select, input[type="button"],
@ -72,7 +75,7 @@ input[type="reset"], input[type="submit"] {
button { button {
color: #FFF; color: #FFF;
background-color: $buttonBackground !important; background-color: $buttonBackground;
border-radius: $borderRadius; border-radius: $borderRadius;
} }
@ -159,6 +162,9 @@ form {
display: flex !important; display: flex !important;
} }
/**
* Tooltips
**/
.tipsy { .tipsy {
z-index: $tooltipsZ; z-index: $tooltipsZ;
&-inner { &-inner {
@ -169,3 +175,26 @@ form {
border-color: $tooltipBg; 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 { > ul#contacts {
font-size: 12px; font-size: 12px;
bottom: 0px; bottom: 0px;
margin: 0px; margin: 0;
margin-top: 16px;
padding: 0px; padding: 0px;
width: 100%; width: 100%;
overflow: auto; overflow: auto;
@ -24,7 +25,7 @@
white-space: nowrap; white-space: nowrap;
color: #FFF; color: #FFF;
font-size: 10pt; font-size: 10pt;
padding: 6px 10%; padding: 6px 30px;
&:hover, &:hover,
&:active { &: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; animation: $animations;
} }
@mixin flex() {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
}
/** /**
* Keyframes mixin. * Keyframes mixin.
*/ */
@ -63,3 +71,9 @@
-webkit-transition: $transition; -webkit-transition: $transition;
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%; width: 100%;
} }
.jqibuttons button {
margin-right: 5px;
float:right;
}
button.jqidefaultbutton #inviteLinkRef { button.jqidefaultbutton #inviteLinkRef {
color: #2c8ad2; color: #2c8ad2;
} }

View File

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

View File

@ -1,3 +1,8 @@
/**
* Theme
*/
@import 'themes/light';
/** /**
* Style variables * Style variables
*/ */
@ -21,7 +26,8 @@ $thumbnailToolbarHeight: 25px;
*/ */
$defaultColor: #F1F1F1; $defaultColor: #F1F1F1;
$defaultSideBarFontColor: #44A5FF; $defaultSideBarFontColor: #44A5FF;
$defaultDarkColor: #4F4F4F; $defaultSemiDarkColor: #ACACAC;
$defaultDarkColor: #2b3d5c;
$defaultBackground: #474747; $defaultBackground: #474747;
$tooltipBg: rgba(0,0,0, 0.7); $tooltipBg: rgba(0,0,0, 0.7);
@ -33,11 +39,8 @@ $toolbarBadgeColor: #FFFFFF;
$toolbarToggleBackground: #12499C; $toolbarToggleBackground: #12499C;
// Main controls // Main controls
$inputBackground: rgba(132, 132, 132, .5);
$inputSemiBackground: rgba(132, 132, 132, .8); $inputSemiBackground: rgba(132, 132, 132, .8);
$inputLightBackground: #EBEBEB; $inputLightBackground: #EBEBEB;
$inputBorderColor: #EBEBEB;
$buttonBackground: #44A5FF;
// Video layout. // Video layout.
$videoThumbnailHovered: #BFEBFF; $videoThumbnailHovered: #BFEBFF;
@ -58,10 +61,25 @@ $rateStarLabelColor: #333;
*/ */
$borderRadius: 4px; $borderRadius: 4px;
$defaultWatermarkLink: '../images/watermark.png'; $defaultWatermarkLink: '../images/watermark.png';
$sidebarWidth: 200px;
/** /**
* Z-indexes. TODO: Replace this by a function. * Z-indexes. TODO: Replace this by a function.
*/ */
$tooltipsZ: 901; $tooltipsZ: 901;
$toolbarZ: 900; $toolbarZ: 900;
$overlayZ: 800; $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 */ /* Variables BEGIN */
@import 'variables'; @import 'variables';
@ -10,6 +16,12 @@
/* Mixins END */ /* Mixins END */
/* Animations BEGIN */
@import "animations";
/* Animations END */
/* Fonts BEGIN */ /* Fonts BEGIN */
@import 'font'; @import 'font';
@ -17,6 +29,10 @@
/* Fonts END */ /* Fonts END */
/* Theme BEGIN */
@import "themes/light";
/* Theme END */
/* Modules BEGIN */ /* Modules BEGIN */
@import 'toastr'; @import 'toastr';
@ -25,8 +41,6 @@
@import 'modals/dialog'; @import 'modals/dialog';
@import 'modals/feedback/feedback'; @import 'modals/feedback/feedback';
@import 'videolayout_default'; @import 'videolayout_default';
@import 'jquery-impromptu';
@import 'modaldialog';
@import 'notice'; @import 'notice';
@import 'popup_menu'; @import 'popup_menu';
@import 'recording'; @import 'recording';
@ -43,6 +57,8 @@
@import 'jquery.contextMenu'; @import 'jquery.contextMenu';
@import 'keyboard-shortcuts'; @import 'keyboard-shortcuts';
@import 'redirect_page'; @import 'redirect_page';
@import 'input-control/input-control';
@import 'shortcuts/main';
@import 'buttons/button-control';
/* Modules END */ /* Modules END */

View File

@ -1,53 +1,80 @@
.dialog{ .dialog {
visibility: visible; visibility: visible;
height: auto; height: auto;
p { h3 {
color: $defaultDarkColor; color: $auiDialogColor;
} }
textarea {
background: none;
border: 1px solid $inputBorderColor;
}
.aui-dialog2-content:last-child {
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
}
.aui-dialog2-content: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 { .aui {
font-weight: 400 !important;
color: $buttonBackground;
background: none !important;
:hover { &-icon {
text-decoration: underline; color: $auiDialogColor;
&-small {
width: 14px;
height: 14px;
} }
} }
&_submit {
font-weight: 700 !important; &-iconfont-close-dialog {
color: $defaultColor; cursor: pointer;
background: $buttonBackground; right: 20px;
border-radius: 3px; 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;
}
&:first-child {
border-top-right-radius: 5px;
border-top-left-radius: 5px;
}
}
} }
} }
}
.input-control:not(:last-child) {
border-bottom: 1px solid $auiBorderColor;
}
}

View File

@ -45,64 +45,60 @@
animation-timing-function: ease-in-out animation-timing-function: ease-in-out
} }
.feedback { .feedback.aui-dialog2{
h2 { .aui-dialog2{
font-weight: 400; &-header {
font-size: 24px; background-color: $auiDialogContentBg;
line-height: 1.2; border-bottom-color: transparent;
} padding-top: 30px;
p { h2 {
font-weight: 400; text-align: center;
font-size: 14px; }
}
&__content {
text-align: center;
textarea {
text-align: left;
min-height: 80px;
width: 100%;
}
}
&__footer {
&:hover {
color: #287ade;
outline: 0;
}
}
&__rating {
line-height: 1.2;
padding: 20px 0;
p {
margin: 10px 0 0;
} }
.star-label { &-content {
font-size: 16px; text-align: center;
color: $rateStarLabelColor; padding: 10px 40px 20px 40px;
}
.star-btn { .rating {
color: $rateStarDefault; line-height: 1.2;
font-size: 36px; text-align: center;
position: relative; margin-top: 10px;
cursor: pointer;
outline: none;
text-decoration: none;
@include transition(all .2s ease);
&.starHover, .star-label {
&.active, height: 16px;
&:hover { font-size: 14px;
color: $rateStarActivity;
> i:before {
content: "\e90a";
} }
}; .star-btn {
color: $rateStarDefault;
font-size: 36px;
position: relative;
cursor: pointer;
outline: none;
text-decoration: none;
@include transition(all .2s ease);
&.starHover,
&.active,
&:hover {
color: $rateStarActivity;
};
}
}
.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> </span>
</a> </a>
<a class="button" id="toolbar_button_record" style="display: none"></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-share-doc" id="toolbar_button_etherpad"></a>
<a class="button icon-shared-video" id="toolbar_button_sharedvideo" style="display: none"> <a class="button icon-shared-video" id="toolbar_button_sharedvideo" style="display: none">
<ul id="sharedVideoMutedPopup" class="loginmenu extendedToolbarPopup"> <ul id="sharedVideoMutedPopup" class="loginmenu extendedToolbarPopup">
@ -254,12 +253,10 @@
</div> </div>
</div> </div>
<div id="keyboard-shortcuts" class="keyboard-shortcuts" style="display:none;"> <div id="keyboard-shortcuts" class="keyboard-shortcuts" style="display:none;">
<div class="header"><h3 data-i18n="keyboardShortcuts.keyboardShortcuts"></h3></div>
<div class="content"> <div class="content">
<ul id="keyboard-shortcuts-list" class="item"> <ul id="keyboard-shortcuts-list" class="shortcuts-list">
</ul> </ul>
</div> </div>
</div> </div>
<div id="aui-feedback-dialog" class="dialog feedback aui-layer aui-dialog2 aui-dialog2-medium" style="display: none;"></div>
</body> </body>
</html> </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", "connectionsettings": "Connection Settings",
"poweredby": "powered by", "poweredby": "powered by",
"feedback": "Give us your feedback", "feedback": "Give us your feedback",
"roomUrlDefaultMsg": "Your conference is currently being created...", "inviteUrlDefaultMsg": "Your conference is currently being created...",
"me": "me", "me": "me",
"speaker": "Speaker", "speaker": "Speaker",
"raisedHand": "Would like to speak", "raisedHand": "Would like to speak",
@ -180,8 +184,10 @@
"raisedHand": "Would like to speak." "raisedHand": "Would like to speak."
}, },
"dialog": { "dialog": {
"add": "Add",
"kickMessage": "Ouch! You have been kicked out of the meet!", "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.", "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.", "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.", "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.", "connectError": "Oops! Something went wrong and we couldn't connect to the conference.",
@ -189,6 +195,7 @@
"connecting": "Connecting", "connecting": "Connecting",
"copy": "Copy", "copy": "Copy",
"error": "Error", "error": "Error",
"addPassword": "Add Password",
"detectext": "Error when trying to detect desktopsharing extension.", "detectext": "Error when trying to detect desktopsharing extension.",
"failtoinstall": "Failed to install desktop sharing extension", "failtoinstall": "Failed to install desktop sharing extension",
"failedpermissions": "Failed to obtain permissions to use the local microphone and/or camera.", "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)", "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)", "SRDFailure": "Oops! Something went wrong and we failed to stop video! (SRD Failure)",
"oops": "Oops!", "oops": "Oops!",
"currentPassword": "The current password is",
"passwordLabel": "Password",
"defaultError": "There was some kind of error", "defaultError": "There was some kind of error",
"passwordRequired": "Password required", "passwordRequired": "Password required",
"Ok": "Ok", "Ok": "Ok",
"Remove": "Remove", "Remove": "Remove",
"removePassword": "Remove password",
"shareVideoTitle": "Share a video", "shareVideoTitle": "Share a video",
"shareVideoLinkError": "Please provide a correct youtube link.", "shareVideoLinkError": "Please provide a correct youtube link.",
"removeSharedVideoTitle": "Remove shared video", "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.", "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", "IamHost": "I am the host",
"Cancel": "Cancel", "Cancel": "Cancel",
"Submit": "Submit",
"retry": "Retry", "retry": "Retry",
"logoutTitle" : "Logout", "logoutTitle" : "Logout",
"logoutQuestion" : "Are you sure you want to logout and stop the conference?", "logoutQuestion" : "Are you sure you want to logout and stop the conference?",
@ -231,7 +242,6 @@
"Dial": "Dial", "Dial": "Dial",
"sipMsg": "Enter SIP number", "sipMsg": "Enter SIP number",
"passwordCheck": "Are you sure you would like to remove your password?", "passwordCheck": "Are you sure you would like to remove your password?",
"Remove": "Remove",
"passwordMsg": "Set a password to lock your room", "passwordMsg": "Set a password to lock your room",
"shareLink": "Copy and share this link", "shareLink": "Copy and share this link",
"settings1": "Configure your conference", "settings1": "Configure your conference",

View File

@ -4,7 +4,6 @@ var UI = {};
import Chat from "./side_pannels/chat/Chat"; import Chat from "./side_pannels/chat/Chat";
import Toolbar from "./toolbars/Toolbar"; import Toolbar from "./toolbars/Toolbar";
import ToolbarToggler from "./toolbars/ToolbarToggler"; import ToolbarToggler from "./toolbars/ToolbarToggler";
import ContactList from "./side_pannels/contactlist/ContactList";
import Avatar from "./avatar/Avatar"; import Avatar from "./avatar/Avatar";
import SideContainerToggler from "./side_pannels/SideContainerToggler"; import SideContainerToggler from "./side_pannels/SideContainerToggler";
import UIUtil from "./util/UIUtil"; import UIUtil from "./util/UIUtil";
@ -29,7 +28,6 @@ UI.messageHandler = require("./util/MessageHandler");
var messageHandler = UI.messageHandler; var messageHandler = UI.messageHandler;
var JitsiPopover = require("./util/JitsiPopover"); var JitsiPopover = require("./util/JitsiPopover");
var Feedback = require("./feedback/Feedback"); var Feedback = require("./feedback/Feedback");
import FollowMe from "../FollowMe"; import FollowMe from "../FollowMe";
var eventEmitter = new EventEmitter(); var eventEmitter = new EventEmitter();
@ -242,7 +240,7 @@ UI.showChatError = function (err, msg) {
* @param {string} displayName new nickname * @param {string} displayName new nickname
*/ */
UI.changeDisplayName = function (id, displayName) { UI.changeDisplayName = function (id, displayName) {
ContactList.onDisplayNameChange(id, displayName); UI.ContactList.onDisplayNameChange(id, displayName);
VideoLayout.onDisplayNameChanged(id, displayName); VideoLayout.onDisplayNameChanged(id, displayName);
if (APP.conference.isLocalId(id) || id === 'localVideoContainer') { if (APP.conference.isLocalId(id) || id === 'localVideoContainer') {
@ -292,14 +290,16 @@ UI.initConference = function () {
// "https:" + "//" + "example.com:8888" + "/SomeConference1245" // "https:" + "//" + "example.com:8888" + "/SomeConference1245"
var inviteURL = window.location.protocol + "//" + var inviteURL = window.location.protocol + "//" +
window.location.host + window.location.pathname; window.location.host + window.location.pathname;
Toolbar.updateRoomUrl(inviteURL);
this.emitEvent(UIEvents.INVITE_URL_INITIALISED, inviteURL);
// Clean up the URL displayed by the browser // Clean up the URL displayed by the browser
if (window.history && typeof window.history.replaceState === 'function') { if (window.history && typeof window.history.replaceState === 'function') {
window.history.replaceState({}, document.title, inviteURL); window.history.replaceState({}, document.title, inviteURL);
} }
// Add myself to the contact list. // Add myself to the contact list.
ContactList.addContact(id, true); UI.ContactList.addContact(id, true);
// Update default button states before showing the toolbar // Update default button states before showing the toolbar
// if local role changes buttons state will be again updated. // if local role changes buttons state will be again updated.
@ -470,8 +470,6 @@ UI.start = function () {
} }
VideoLayout.resizeVideoArea(true, true); VideoLayout.resizeVideoArea(true, true);
ContactList.init(eventEmitter);
bindEvents(); bindEvents();
sharedVideoManager = new SharedVideoManager(eventEmitter); sharedVideoManager = new SharedVideoManager(eventEmitter);
if (!interfaceConfig.filmStripOnly) { if (!interfaceConfig.filmStripOnly) {
@ -608,7 +606,7 @@ UI.addUser = function (user) {
var id = user.getId(); var id = user.getId();
var displayName = user.getDisplayName(); var displayName = user.getDisplayName();
UI.hideRingOverLay(); UI.hideRingOverLay();
ContactList.addContact(id); UI.ContactList.addContact(id);
messageHandler.notify( messageHandler.notify(
displayName,'notify.somebody', 'connected', 'notify.connected' displayName,'notify.somebody', 'connected', 'notify.connected'
@ -635,7 +633,7 @@ UI.addUser = function (user) {
* @param {string} displayName user nickname * @param {string} displayName user nickname
*/ */
UI.removeUser = function (id, displayName) { UI.removeUser = function (id, displayName) {
ContactList.removeContact(id); UI.ContactList.removeContact(id);
messageHandler.notify( messageHandler.notify(
displayName,'notify.somebody', 'disconnected', 'notify.disconnected' displayName,'notify.somebody', 'disconnected', 'notify.disconnected'
@ -786,28 +784,33 @@ UI.connectionIndicatorShowMore = function(id) {
// FIXME check if someone user this // FIXME check if someone user this
UI.showLoginPopup = function(callback) { UI.showLoginPopup = function(callback) {
console.log('password is required'); console.log('password is required');
var message = '<h2 data-i18n="dialog.passwordRequired">'; let titleKey = "dialog.passwordRequired";
message += APP.translation.translateString( let titleString = APP.translation.translateString(titleKey);
"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) {
if (v) {
if (f.username && f.password) {
callback(f.username, f.password);
}
}
},
null, null, ':input:first'
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);
}
}
};
messageHandler.openTwoButtonDialog({
titleKey,
titleString,
msgString: message,
leftButtonKey: 'dialog.Ok',
submitFunction,
focus: ':input:first'
});
}; };
UI.askForNickname = function () { UI.askForNickname = function () {
@ -888,7 +891,7 @@ UI.dockToolbar = function (isDock) {
*/ */
function changeAvatar(id, avatarUrl) { function changeAvatar(id, avatarUrl) {
VideoLayout.changeUserAvatar(id, avatarUrl); VideoLayout.changeUserAvatar(id, avatarUrl);
ContactList.changeUserAvatar(id, avatarUrl); UI.ContactList.changeUserAvatar(id, avatarUrl);
if (APP.conference.isLocalId(id)) { if (APP.conference.isLocalId(id)) {
Profile.changeAvatar(avatarUrl); 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. * Add chat message.
* @param {string} from user id * @param {string} from user id
@ -1254,24 +1245,27 @@ UI.showExtensionRequiredDialog = function (url) {
* @param url {string} the url of the extension. * @param url {string} the url of the extension.
*/ */
UI.showExtensionExternalInstallationDialog = function (url) { UI.showExtensionExternalInstallationDialog = function (url) {
messageHandler.openTwoButtonDialog( let submitFunction = function(e,v){
"dialog.externalInstallationTitle", if (v) {
null, e.preventDefault();
"dialog.externalInstallationMsg", eventEmitter.emit(UIEvents.OPEN_EXTENSION_STORE, url);
null,
true,
"dialog.goToStore",
function(e,v) {
if (v) {
e.preventDefault();
eventEmitter.emit(UIEvents.OPEN_EXTENSION_STORE, url);
}
},
function () {},
function () {
eventEmitter.emit(UIEvents.EXTERNAL_INSTALLATION_CANCELED);
} }
); };
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.' * Shows or hides the keyboard shortcuts panel, depending on the current state.'
*/ */
UI.toggleKeyboardShortcutsPanel = function() { 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 */ /* global $, APP, JitsiMeetJS */
import UIEvents from "../../../service/UI/UIEvents"; import UIEvents from "../../../service/UI/UIEvents";
import FeedabckWindow from "./FeedbackWindow"; import FeedbackWindow from "./FeedbackWindow";
/** /**
* Shows / hides the feedback button. * Shows / hides the feedback button.
@ -49,7 +49,7 @@ var Feedback = {
_showFeedbackButton(this.enabled); _showFeedbackButton(this.enabled);
this.window = new FeedabckWindow({}); this.window = new FeedbackWindow();
$("#feedbackButton").click(Feedback.openFeedbackWindow); $("#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 * 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 * @param starCount the number of stars, for which to toggle the css class
*/ */
function toggleStars(starCount) { function toggleStars(starCount) {
let labelEl = $('#starLabel');
let label = starCount >= 0 ?
labels[starCount + 1] :
'';
$('#stars > a').each(function(index, el) { $('#stars > a').each(function(index, el) {
if (index <= starCount) { if (index <= starCount) {
el.classList.add("starHover"); el.classList.add("starHover");
} else } else
el.classList.remove("starHover"); el.classList.remove("starHover");
}); });
labelEl.text(label);
} }
/** /**
@ -23,63 +35,51 @@ function toggleStars(starCount) {
* @returns {string} the contructed html string * @returns {string} the contructed html string
*/ */
function createRateFeedbackHTML() { function createRateFeedbackHTML() {
let rateExperience let feedbackHelp = APP.translation.translateString('dialog.feedbackHelp');
= APP.translation.translateString('dialog.rateExperience'),
feedbackHelp = APP.translation.translateString('dialog.feedbackHelp');
let starClassName = (interfaceConfig.ENABLE_FEEDBACK_ANIMATION) let starClassName = (interfaceConfig.ENABLE_FEEDBACK_ANIMATION)
? "icon-star shake-rotate" ? "icon-star-full shake-rotate"
: "icon-star"; : "icon-star-full";
return ` return `
<div class="aui-dialog2-content feedback__content"> <form id="feedbackForm"
<form action="javascript:false;" onsubmit="return false;"> action="javascript:false;" onsubmit="return false;">
<div class="feedback__rating"> <div class="rating">
<h2>${ rateExperience }</h2> <div class="star-label">
<p class="star-label">&nbsp;</p> <p id="starLabel">&nbsp;</p>
<div id="stars" class="feedback-stars">
<a class="star-btn">
<i class=${ starClassName }></i>
</a>
<a class="star-btn">
<i class=${ starClassName }></i>
</a>
<a class="star-btn">
<i class=${ starClassName }></i>
</a>
<a class="star-btn">
<i class=${ starClassName }></i>
</a>
<a class="star-btn">
<i class=${ starClassName }></i>
</a>
</div>
<p>&nbsp;</p>
<p>${ feedbackHelp }</p>
</div> </div>
<textarea id="feedbackTextArea" rows="10" cols="40" autofocus> <div id="stars" class="feedback-stars">
</textarea> <a class="star-btn">
</form> <i class=${ starClassName }></i>
<footer class="aui-dialog2-footer feedback__footer"> </a>
<div class="aui-dialog2-footer-actions"> <a class="star-btn">
<button <i class=${ starClassName }></i>
id="dialog-close-button" </a>
class="aui-button aui-button_close">Close</button> <a class="star-btn">
<button <i class=${ starClassName }></i>
id="dialog-submit-button" </a>
class="aui-button aui-button_submit">Submit</button> <a class="star-btn">
<i class=${ starClassName }></i>
</a>
<a class="star-btn">
<i class=${ starClassName }></i>
</a>
</div> </div>
</footer> </div>
</div> <div class="details">
`; <textarea id="feedbackTextArea" class="input-control__input"
placeholder="${ feedbackHelp }"></textarea>
</div>
</form>`;
} }
/** /**
* Callback for Rate Feedback * Feedback is loaded callback
* Calls when Modal window is in DOM
* *
* @param Feedback * @param Feedback
*/ */
let onLoadRateFunction = function (Feedback) { let onLoadFunction = function (Feedback) {
$('#stars > a').each((index, el) => { $('#stars > a').each((index, el) => {
el.onmouseover = function(){ el.onmouseover = function(){
toggleStars(index); toggleStars(index);
@ -89,6 +89,7 @@ let onLoadRateFunction = function (Feedback) {
}; };
el.onclick = function(){ el.onclick = function(){
Feedback.feedbackScore = index + 1; Feedback.feedbackScore = index + 1;
Feedback.setFeedbackMessage();
}; };
}); });
@ -97,101 +98,88 @@ let onLoadRateFunction = function (Feedback) {
toggleStars(Feedback.feedbackScore - 1); toggleStars(Feedback.feedbackScore - 1);
} }
if (Feedback.feedbackText && Feedback.feedbackText.length > 0) if (Feedback.feedbackMessage && Feedback.feedbackMessage.length > 0)
$('#feedbackTextArea').text(Feedback.feedbackText); $('#feedbackTextArea').text(Feedback.feedbackMessage);
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();
});
}
$('#feedbackTextArea').focus(); $('#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 * @class Dialog
* *
*/ */
export default class Dialog { export default class Dialog {
constructor(options) { constructor() {
this.feedbackScore = -1; this.feedbackScore = -1;
this.feedbackText = null; this.feedbackMessage = '';
this.submitted = false; this.submitted = false;
this.onCloseCallback = null; this.onCloseCallback = function() {};
this.states = { this.setDefoulOptions();
rate_feedback: {
getHtml: createRateFeedbackHTML,
onLoad: onLoadRateFunction
}
};
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) { setDefoulOptions() {
let newState = state || this.state; var self = this;
let htmlStr = this.states[newState].getHtml(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.$el.html(htmlStr); setFeedbackMessage() {
let message = $('#feedbackTextArea').val();
this.states[newState].onLoad(this); this.feedbackMessage = message;
} }
show(cb) { show(cb) {
this.setState('rate_feedback'); const options = this.options;
if (typeof cb == 'function') { if (typeof cb === 'function') {
this.onCloseCallback = cb; this.onCloseCallback = cb;
} }
this.window.show(); this.window = APP.UI.messageHandler.openTwoButtonDialog(options);
} }
hide() { onHide() {
this.window.hide(); this.onCloseCallback(this.feedbackScore, this.feedbackMessage);
}
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();
} }
} }

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

View File

@ -722,26 +722,25 @@ function getYoutubeLink(url) {
*/ */
function showStopVideoPropmpt() { function showStopVideoPropmpt() {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
dialog = APP.UI.messageHandler.openTwoButtonDialog( let submitFunction = function(e,v) {
"dialog.removeSharedVideoTitle", if (v) {
null, resolve();
"dialog.removeSharedVideoMsg", } else {
null, reject();
false,
"dialog.Remove",
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) { return new Promise(function (resolve, reject) {
dialog = APP.UI.messageHandler.openDialogWithStates({ dialog = APP.UI.messageHandler.openDialogWithStates({
state0: { state0: {
title: title,
html: ` html: `
<h2>${title}</h2>
<input name="sharedVideoUrl" type="text" <input name="sharedVideoUrl" type="text"
data-i18n="[placeholder]defaultLink" data-i18n="[placeholder]defaultLink"
data-i18n-options="${JSON.stringify(i18nOptions)}" data-i18n-options="${JSON.stringify(i18nOptions)}"
@ -803,7 +802,8 @@ function requestVideoLink() {
}, },
state1: { state1: {
html: `<h2>${title}</h2> ${linkError}`, title: title,
html: linkError,
persistent: false, persistent: false,
buttons: [ buttons: [
{title: cancelButton, value: false}, {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 */ /* global APP */
import Avatar from '../../avatar/Avatar';
import UIEvents from '../../../../service/UI/UIEvents'; import UIEvents from '../../../../service/UI/UIEvents';
import UIUtil from '../../util/UIUtil'; import ContactListView from './ContactListView';
import Contact from './Contact';
let numberOfContacts = 0;
/** /**
* Updates the number of participants in the contact list button and sets * Model for the Contact list.
* the glow *
* @param delta indicates whether a new user has joined (1) or someone has * @class ContactList
* left(-1)
*/ */
function updateNumberOfParticipants(delta) { class ContactList {
numberOfContacts += delta; constructor(conference) {
this.conference = conference;
if (numberOfContacts <= 0) { this.contacts = [];
console.error("Invalid number of participants: " + numberOfContacts); this.roomLocked = false;
return; ContactListView.init(this);
} }
$("#numberOfParticipants").text(numberOfContacts); /**
* Is locked flag.
$("#contacts_container>div.title").text( * Delegates to Invite module
APP.translation.translateString("contactlist") * TO FIX: find a better way to access the IS LOCKED state of the invite.
+ ' (' + numberOfContacts + ')'); *
} * @returns {Boolean}
*/
/** isLocked() {
* Creates the avatar element. return APP.conference.invite.isLocked();
*
* @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;
}
function getContactEl (id) {
return $(`#contacts>li[id="${id}"]`);
}
/**
* 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> - * @param id
* otherwise * @param isLocal
*/ */
isVisible () { addContact(id, isLocal) {
return UIUtil.isVisible(document.getElementById("contactlist")); let isExist = this.contacts.some((el) => el.id === id);
},
/** if (!isExist) {
* Adds a contact for the given id. let newContact = new Contact({ id, isLocal });
* @param isLocal is an id for the local user. this.contacts.push(newContact);
*/ APP.UI.emitEvent(UIEvents.CONTACT_ADDED, { id, isLocal });
addContact (id, isLocal) {
let contactlist = $('#contacts');
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 (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) { removeContact(id) {
let contact = getContactEl(id); this.contacts = this.contacts.filter((el) => el.id !== id);
APP.UI.emitEvent(UIEvents.CONTACT_REMOVED, { id });
return this.contacts;
}
if (contact.length > 0) { /**
contact.remove(); * Changing the display name.
updateNumberOfParticipants(-1); *
} * @param id
}, * @param name
*/
setClickable (id, isClickable) { onDisplayNameChange (id, name) {
getContactEl(id).toggleClass('clickable', isClickable); if(!name)
},
onDisplayNameChange (id, displayName) {
if(!displayName)
return; return;
if (id === 'localVideoContainer') { if (id === 'localVideoContainer') {
id = APP.conference.getMyUserId(); id = APP.conference.getMyUserId();
} }
let contactName = $(`#contacts #${id}>p`);
if (contactName.text() !== displayName) { let contacts = this.contacts.filter((el) => el.id === id);
contactName.text(displayName); 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);
}
} }
};
export default ContactList; /**
* 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 UIEvents from '../../../service/UI/UIEvents';
import SideContainerToggler from "../side_pannels/SideContainerToggler"; import SideContainerToggler from "../side_pannels/SideContainerToggler";
let roomUrl = null;
let emitter = null; let emitter = null;
let Toolbar;
/**
* 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.
);
}
const buttonHandlers = { const buttonHandlers = {
"toolbar_button_profile": function () { "toolbar_button_profile": function () {
@ -98,13 +42,9 @@ const buttonHandlers = {
emitter.emit(UIEvents.VIDEO_MUTED, true); 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 () { "toolbar_button_link": function () {
JitsiMeetJS.analytics.sendEvent('toolbar.invite.clicked'); JitsiMeetJS.analytics.sendEvent('toolbar.invite.clicked');
openLinkDialog(); emitter.emit(UIEvents.INVITE_CLICKED);
}, },
"toolbar_button_chat": function () { "toolbar_button_chat": function () {
JitsiMeetJS.analytics.sendEvent('toolbar.chat.toggled'); JitsiMeetJS.analytics.sendEvent('toolbar.chat.toggled');
@ -158,21 +98,20 @@ const buttonHandlers = {
emitter.emit(UIEvents.AUTH_CLICKED); emitter.emit(UIEvents.AUTH_CLICKED);
}, },
"toolbar_button_logout": function () { "toolbar_button_logout": function () {
let titleKey = "dialog.logoutTitle";
let msgKey = "dialog.logoutQuestion";
JitsiMeetJS.analytics.sendEvent('toolbar.authenticate.logout.clicked'); JitsiMeetJS.analytics.sendEvent('toolbar.authenticate.logout.clicked');
// Ask for confirmation // Ask for confirmation
APP.UI.messageHandler.openTwoButtonDialog( APP.UI.messageHandler.openTwoButtonDialog({
"dialog.logoutTitle", titleKey,
null, msgKey,
"dialog.logoutQuestion", leftButtonKey: "dialog.Yes",
null, submitFunction: function (evt, yes) {
false,
"dialog.Yes",
function (evt, yes) {
if (yes) { if (yes) {
emitter.emit(UIEvents.LOGOUT); emitter.emit(UIEvents.LOGOUT);
} }
} }
); });
}, },
"toolbar_film_strip": function () { "toolbar_film_strip": function () {
JitsiMeetJS.analytics.sendEvent( JitsiMeetJS.analytics.sendEvent(
@ -246,10 +185,6 @@ const defaultToolbarButtons = {
content: 'Share screen', content: 'Share screen',
i18n: '[content]toolbar.sharescreen' i18n: '[content]toolbar.sharescreen'
}, },
'security': {
id: 'toolbar_button_security',
tooltipKey: 'toolbar.lock'
},
'invite': { 'invite': {
id: 'toolbar_button_link', id: 'toolbar_button_link',
tooltipKey: 'toolbar.invite', tooltipKey: 'toolbar.invite',
@ -344,27 +279,28 @@ function showSipNumberInput () {
let defaultNumber = config.defaultSipNumber let defaultNumber = config.defaultSipNumber
? config.defaultSipNumber ? config.defaultSipNumber
: ''; : '';
let titleKey = "dialog.sipMsg";
let sipMsg = APP.translation.generateTranslationHTML("dialog.sipMsg"); let sipMsg = APP.translation.generateTranslationHTML("dialog.sipMsg");
APP.UI.messageHandler.openTwoButtonDialog( let msgString = (`
null, null, null, <input name="sipNumber" type="text"
`<h2>${sipMsg}</h2> value="${defaultNumber}" autofocus>
<input `);
name="sipNumber"
type="text" APP.UI.messageHandler.openTwoButtonDialog({
value="${defaultNumber}" titleKey,
autofocus>`, titleString: sipMsg,
false, "dialog.Dial", msgString,
function (e, v, m, f) { leftButtonKey: "dialog.Dial",
submitFunction: function (e, v, m, f) {
if (v && f.sipNumber) { if (v && f.sipNumber) {
emitter.emit(UIEvents.SIP_DIAL, f.sipNumber); emitter.emit(UIEvents.SIP_DIAL, f.sipNumber);
} }
}, },
null, null, ':input:first' focus: ':input:first'
); });
} }
const Toolbar = { Toolbar = {
init (eventEmitter) { init (eventEmitter) {
emitter = eventEmitter; emitter = eventEmitter;
// The toolbar is enabled by default. // The toolbar is enabled by default.
@ -446,41 +382,6 @@ const Toolbar = {
isEnabled() { isEnabled() {
return this.enabled; 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 * Shows or hides authentication button
* @param show <tt>true</tt> to show or <tt>false</tt> to hide * @param show <tt>true</tt> to show or <tt>false</tt> to hide

View File

@ -52,8 +52,10 @@ var messageHandler = {
} }
return $.prompt(message, { return $.prompt(message, {
title: title, title: this._getFormattedTitleString(title),
persistent: false, persistent: false,
promptspeed: 0,
classes: this._getDialogClasses(),
close: function (e, v, m, f) { close: function (e, v, m, f) {
if(closeFunction) if(closeFunction)
closeFunction(e, v, m, f); closeFunction(e, v, m, f);
@ -79,16 +81,31 @@ var messageHandler = {
* the user press 'enter'. Indexed from 0. * the user press 'enter'. Indexed from 0.
* @return the prompt that was created, or null * @return the prompt that was created, or null
*/ */
openTwoButtonDialog: function(titleKey, titleString, msgKey, msgString, openTwoButtonDialog: function(options) {
persistent, leftButtonKey, submitFunction, loadedFunction, let {
closeFunction, focus, defaultButton) { titleKey,
titleString,
msgKey,
msgString,
leftButtonKey,
submitFunction,
loadedFunction,
closeFunction,
focus,
size,
defaultButton,
wrapperClass,
classes
} = options;
if (!popupEnabled || twoButtonDialog) if (!popupEnabled || twoButtonDialog)
return null; return null;
var buttons = []; 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}); buttons.push({ title: leftButton, value: true});
var cancelButton var cancelButton
@ -102,22 +119,32 @@ var messageHandler = {
if (msgKey) { if (msgKey) {
message = APP.translation.generateTranslationHTML(msgKey); message = APP.translation.generateTranslationHTML(msgKey);
} }
classes = classes || this._getDialogClasses(size);
if (wrapperClass) {
classes.prompt += ` ${wrapperClass}`;
}
twoButtonDialog = $.prompt(message, { twoButtonDialog = $.prompt(message, {
title: title, title: this._getFormattedTitleString(title),
persistent: false, persistent: false,
buttons: buttons, buttons: buttons,
defaultButton: defaultButton, defaultButton: defaultButton,
focus: focus, focus: focus,
loaded: loadedFunction, loaded: loadedFunction,
promptspeed: 0,
classes,
submit: function (e, v, m, f) { submit: function (e, v, m, f) {
twoButtonDialog = null; twoButtonDialog = null;
if (submitFunction) if (v){
submitFunction(e, v, m, f); if (submitFunction)
submitFunction(e, v, m, f);
}
}, },
close: function (e, v, m, f) { close: function (e, v, m, f) {
twoButtonDialog = null; twoButtonDialog = null;
if (closeFunction) if (closeFunction) {
closeFunction(e, v, m, f); closeFunction(e, v, m, f);
}
} }
}); });
return twoButtonDialog; return twoButtonDialog;
@ -144,14 +171,16 @@ var messageHandler = {
if (!popupEnabled) if (!popupEnabled)
return; return;
var args = { let args = {
title: titleString, title: this._getFormattedTitleString(titleString),
persistent: persistent, persistent: persistent,
buttons: buttons, buttons: buttons,
defaultButton: 1, defaultButton: 1,
promptspeed: 0,
loaded: loadedFunction, loaded: loadedFunction,
submit: submitFunction, submit: submitFunction,
close: closeFunction close: closeFunction,
classes: this._getDialogClasses()
}; };
if (persistent) { if (persistent) {
@ -161,6 +190,40 @@ var messageHandler = {
return new Impromptu(msgString, args); 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. * Closes currently opened dialog.
*/ */
@ -176,7 +239,18 @@ var messageHandler = {
openDialogWithStates: function (statesObject, options) { openDialogWithStates: function (statesObject, options) {
if (!popupEnabled) if (!popupEnabled)
return; 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); return new Impromptu(statesObject, options);
}, },
@ -210,6 +284,7 @@ var messageHandler = {
} }
}, 200); }, 200);
} }
return popup; return popup;
}, },

View File

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

View File

@ -22,13 +22,16 @@ export default {
VIDEO_MUTED: "UI.video_muted", VIDEO_MUTED: "UI.video_muted",
ETHERPAD_CLICKED: "UI.etherpad_clicked", ETHERPAD_CLICKED: "UI.etherpad_clicked",
SHARED_VIDEO_CLICKED: "UI.start_shared_video", 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) * Updates shared video with params: url, state, time(optional)
* Where url is the video link, state is stop/start/pause and time is the * Where url is the video link, state is stop/start/pause and time is the
* current video playing time. * current video playing time.
*/ */
UPDATE_SHARED_VIDEO: "UI.update_shared_video", UPDATE_SHARED_VIDEO: "UI.update_shared_video",
ROOM_LOCK_CLICKED: "UI.room_lock_clicked",
USER_KICKED: "UI.user_kicked", USER_KICKED: "UI.user_kicked",
REMOTE_AUDIO_MUTED: "UI.remote_audio_muted", REMOTE_AUDIO_MUTED: "UI.remote_audio_muted",
FULLSCREEN_TOGGLE: "UI.fullscreen_toggle", FULLSCREEN_TOGGLE: "UI.fullscreen_toggle",
@ -114,5 +117,40 @@ export default {
/** /**
* Notifies that the avatar is displayed or not on the largeVideo. * 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"
}; };