Compare commits

..

90 Commits
1237 ... 1280

Author SHA1 Message Date
yanas
4db8faa526 Merge pull request #914 from jitsi/fix_toolbar_ringoverlay
fix(toolbar): Issue with toolbar is positioned under the ring overlay
2016-09-22 10:12:38 -05:00
Дамян Минков
586ea2ae0d Merge pull request #911 from jitsi/small_videolayout_refactoring
Small videolayout refactoring
2016-09-22 09:45:15 -05:00
paweldomas
e39648ce21 ref(LargeVideo): rename to LargeVideoManager
It is confusing when the name of the main class exported from the file
is not the same as the filename.
2016-09-22 08:57:14 -05:00
paweldomas
e0a05c5908 ref(LargeVideo): move VideoContainer to separate file
VideoContainer is a separate being which implements the LargeContainer
and it's confusing to have it in the same file. This was encouraging to
access private parts of the VideoContainer directly(not through
the interface).
2016-09-22 08:57:14 -05:00
paweldomas
2c01fde713 ref(SmallVideo): rename 'isMuted' to avoid confusion 2016-09-22 08:57:01 -05:00
yanas
92641c20f3 Merge pull request #916 from jitsi/contaclist-title-update
Moves the number in the code, not as a translation parameter.
2016-09-21 22:15:48 -05:00
yanas
f145d98a12 Merge pull request #915 from jitsi/update-tooltip-from-profile
Update tooltip from profile
2016-09-21 18:15:54 -05:00
yanas
a3d2c95d80 Merge pull request #913 from jitsi/adjust-feedback-question
Adjusts feedback dialog question.
2016-09-21 18:01:31 -05:00
yanas
a5a9936e25 Adjusts feedback text and takes into account comment 2016-09-21 18:00:21 -05:00
damencho
cfeb03740c Moves the number in the code, not as a translation parameter.
There is sometimes problems with cache and late loading of the translations, this commit avoids showing translation key in the contactlist title, by moving the parameter into the code.
2016-09-21 17:16:00 -05:00
damencho
4ab0fca4d7 Removes tooltip from profile avatar when not clickable. 2016-09-21 16:35:42 -05:00
damencho
df3b7e2dbc Adds utility method to remove tooltips. 2016-09-21 16:35:03 -05:00
hristoterezov
302b0cf776 fix(toolbar): Issue with toolbar is positioned under the ring overlay 2016-09-21 16:34:06 -05:00
yanas
e7cbacf9a2 Merge pull request #912 from jitsi/remove-download-logs-link
Remove download logs link
2016-09-21 16:18:18 -05:00
Любомир Маринов
f9817b12bf Merge pull request #910 from jitsi/fix_jitsi_track_error_import
fix(UI): Show the correct message on NO_DATA_FROM_SOURCE GUM errors
2016-09-21 15:57:32 -05:00
damencho
bbc7aedb48 Adds download logs method for debugging.
Adds a download logs method that can be called from console to take usefull messages for debugging.
console> APP.conference.saveLogs();
2016-09-21 15:46:10 -05:00
damencho
4fdc11c6fb Removes download logs references. 2016-09-21 15:45:08 -05:00
hristoterezov
e1cb75fe04 fix(UI): Show the correct message on NO_DATA_FROM_SOURCE GUM errors 2016-09-21 15:31:49 -05:00
yanas
0bf6d52eef Merge pull request #902 from jitsi/fix-authentication-failed-msg
Fixes message for authentication failed.
2016-09-20 12:47:53 -05:00
yanas
7453198472 Fixes message for authentication failed. 2016-09-20 12:08:44 -05:00
Дамян Минков
0c1120c1a8 Merge pull request #897 from jitsi/settings-css-adjustments
Some css and lang adjustments to settings and contact list.
2016-09-20 10:48:57 -05:00
yanas
efbc84d18b Merge pull request #898 from m-voloshin/tooltip-fix-layer
Adjustment for layer order
2016-09-20 10:45:57 -05:00
Дамян Минков
cdca1a46ef Merge pull request #901 from jitsi/remove-missing-method
Removes missing and unused method.
2016-09-20 10:20:23 -05:00
yanas
689f7dc8f3 Merge pull request #900 from jitsi/delete-css-ds_store
Delete css/.DS_Store
2016-09-20 10:17:35 -05:00
yanas
4ca9349de7 Removes missing and unused method. 2016-09-20 10:16:15 -05:00
Lyubomir Marinov
9d6253455a Delete css/.DS_Store 2016-09-20 10:12:56 -05:00
Maxim Voloshin
388f868165 Adjustment for layer order 2016-09-20 13:15:10 +03:00
yanas
aacb39a439 Some css adjustments to settings and contact list. 2016-09-20 01:14:00 -05:00
yanas
298338f076 Merge pull request #896 from jitsi/fix-feedback-dialog
Feedback dialog changes
2016-09-19 23:52:14 -05:00
yanas
2d2915967c Fixes focus in Feedback and makes animation configurable. 2016-09-19 23:25:03 -05:00
Kostiantyn Pashura
4572e1d344 feedback dialog changes 2016-09-19 22:27:41 -05:00
yanas
f8b200f32c Merge pull request #892 from m-voloshin/blue-badges
Blue badges
2016-09-19 22:27:10 -05:00
yanas
3d4addd9ef Fixes badge font and corrects file name. 2016-09-19 22:07:10 -05:00
yanas
2919a60403 Adds blue badges to contact list and chat. 2016-09-19 21:22:41 -05:00
Paweł Domas
a07858cc72 Merge pull request #891 from jitsi/video-thumbnail-toolbar-fixes
Video thumbnail toolbar fixes
2016-09-19 19:31:08 -05:00
yanas
d1d4674136 Fixes some jsdocs. 2016-09-19 18:04:55 -05:00
Paweł Domas
9b5d4b8ceb Merge pull request #856 from jitsi/implement_muted_ended_track_events
feat(UI): Add UI support for camera issue detection
2016-09-19 16:03:06 -05:00
hristoterezov
abe216a0bb feat(UI): Add UI support for camera issue detection 2016-09-19 14:49:50 -05:00
hristoterezov
84983c341e Merge pull request #844 from jitsi/device-selection-rework
Device selection rework
2016-09-19 13:56:21 -05:00
Paweł Domas
075423ee96 Merge pull request #895 from jitsi/fix-incorrect-json-file
Fix incorrect JSON file
2016-09-19 13:29:06 -05:00
Lyubomir Marinov
2ff77676e2 Fix incorrect JSON file
The extension of the file modules/UI/side_pannels/chat/smileys.json
suggests that the format of the file is JSON. However, it contains
JavaScript RegExp instances which do not represent valid JSON. Such
discrepancies between file extension and format cause failures in
certain tools such as Webpack. Convert the file (both extension and
format) into a valid ES2015 module.
2016-09-19 12:48:38 -05:00
yanas
2da3373e10 Merge pull request #890 from jitsi/updates-watermark-link
Updates handling links on watermarks.
2016-09-18 17:02:12 -05:00
yanas
1486eac752 Applies fixes to thumbnail video toolbar from m-voloshin 2016-09-18 16:58:34 -05:00
yanas
c5adecb6e1 Adds menu icon to font. 2016-09-18 16:42:33 -05:00
damencho
e4c4236386 Updates handling links on watermarks.
When link is missing disable clicking.
2016-09-16 15:17:34 -05:00
yanas
e5e7e043ee Merge pull request #889 from BeatC/tooltips-bg
Change color for tooltip
2016-09-16 12:54:15 -05:00
Ilya Daynatovich
f8d01b4312 Change color for tooltip 2016-09-16 19:05:23 +03:00
Дамян Минков
f0a898c674 Merge pull request #885 from jitsi/add-raise-hand-toggle
Adds raise hand toggle state.
2016-09-16 10:28:12 -05:00
yanas
8b7bdb4957 Changes the raise hand event name to fit better the action 2016-09-16 10:22:50 -05:00
yanas
bd46430434 Small color change 2016-09-16 10:22:50 -05:00
yanas
a1635ccc68 Adds javadoc 2016-09-16 10:22:50 -05:00
yanas
1853fa6fae Adds raise hand toggle state. 2016-09-16 10:22:50 -05:00
Дамян Минков
692f0792e1 Merge pull request #886 from jitsi/fix-some-tooltips
Fixes dominant speaker and raised hand tooltips
2016-09-16 10:18:23 -05:00
yanas
bc222c60e0 Fixes dominant speaker and raised hand tooltips 2016-09-16 00:37:29 -05:00
yanas
96bbf0419c Adds tooltips 2016-09-16 00:04:19 -05:00
yanas
531e3d2765 Merge pull request #884 from jitsi/updates-lock-room
Updates lock room
2016-09-15 22:24:58 -05:00
damencho
02165786f1 Handles case where somebody removed lock of the room while we attempt to join.
Receiving password required, marks the room as locked, but if we try to enter without password, mark it as unlocked till we receive a password required error or we successfully join.
2016-09-15 16:34:02 -05:00
damencho
a449223b40 Clears the password if user cancels password prompt.
Clears the password if user cancel attempt to enter password, as using one instance of locker for multiple attempts keeps the password.
2016-09-15 16:32:10 -05:00
yanas
12344ab486 Merge pull request #883 from jitsi/contactlist-displayname
Syncs contactlist display names with thumbnails.
2016-09-15 15:22:47 -05:00
yanas
b6e18d8a68 Merge pull request #882 from jitsi/fix-hide-toolbar
Fixes hiding toolbar.
2016-09-15 15:10:54 -05:00
damencho
1a0677cb59 Syncs contactlist display names with thumbnails.
Uses the same display names in the contact list as in the thumbnails, for local and remote.
2016-09-15 15:01:48 -05:00
damencho
c483587853 Fixes hiding toolbar.
Receiving messages docks the toolbar to be able to see number of unread messages. We need to undock it when we read the messages. We skip undocking if we are not in video mode (on large), cause stuff like youtube video share is docking/undocking the toolbar.
2016-09-15 14:02:56 -05:00
hristoterezov
9b25467080 Merge pull request #878 from jitsi/video-thumbnail-redesign
Video thumbnails redesign
2016-09-15 13:48:07 -05:00
yanas
f37fd15fca Merge pull request #876 from jitsi/ui-ringoverlay-stop
feat(ringoverlay): Stop ringing after 30s and change the message
2016-09-14 21:44:59 -05:00
yanas
5092f52716 Merge pull request #874 from jitsi/ui-fix-ringoverlay
feat(ringoverlay): Change the background when the avatar is displayed
2016-09-14 21:40:54 -05:00
yanas
0013745783 Video thumbnails redesign 2016-09-14 21:20:54 -05:00
hristoterezov
ad5fa13339 feat(ringoverlay): Stop ringing after 30s and change the message 2016-09-14 17:55:43 -05:00
yanas
e1fa5ecb34 Merge pull request #873 from jitsi/chat-updates
Chat updates
2016-09-14 17:08:34 -05:00
damencho
e1512e3776 Fixes focusing on write area or nickname input when chat is open. 2016-09-14 16:47:10 -05:00
hristoterezov
dab5252746 feat(ringoverlay): Change the background when the avatar is displayed 2016-09-14 16:26:17 -05:00
damencho
d8dd564b06 Fixes clearing message counter on opening the chat. 2016-09-14 15:22:36 -05:00
damencho
407b082780 Removes changing the message icon and flashing on new message. 2016-09-14 15:18:14 -05:00
damencho
a671093489 Introduces chat_container_id variable. 2016-09-14 15:13:15 -05:00
Дамян Минков
29f0c0b311 Merge pull request #870 from jitsi/add-raised-hand-icon
Adds raise hand icon
2016-09-14 12:17:06 -05:00
yanas
955680018f Merge pull request #872 from jitsi/fix-hiding-toolbars
Fixes hiding toolbars.
2016-09-14 12:16:57 -05:00
damencho
686e85cd4f Fixes hiding toolbars.
Schedule new hide check if toolbar is hovered, overlay is shown or the sideBar container is visible (chat, contactlist , etc.).
2016-09-14 11:51:47 -05:00
hristoterezov
297d4e65fc style(gitignore): Add .sync-config.cson 2016-09-14 11:38:43 -05:00
yanas
0e94bf7e0b Merge pull request #871 from jitsi/fix-remote-video-thumb
Fixes parameters for VideoLayout.resizeThumbnails.
2016-09-14 11:00:14 -05:00
damencho
a5bc9625ef Fixes parameters for VideoLayout.resizeThumbnails. 2016-09-14 10:48:38 -05:00
yanas
cbde4f89b2 Adds raise hand icon 2016-09-14 09:00:59 -05:00
yanas
891c108191 Merges fix from m-voloshin for Firefox toolbar 2016-09-14 08:25:11 -05:00
yanas
764d767789 Update _variables.scss 2016-09-14 01:22:33 -05:00
yanas
7ded10cd8d Merge pull request #865 from jitsi/add-raise-hand-button
Adds a possibility to add raise hand as a button
2016-09-13 23:09:18 -05:00
yanas
3b05a16b32 Fix for button appearing in both toolbars 2016-09-13 22:10:13 -05:00
yanas
cf49c8c6ff Adds raise hand button to the side toolbar 2016-09-13 21:21:31 -05:00
yanas
99bf4bc44d Adds a possibility to add raise hand as a button 2016-09-13 21:21:31 -05:00
yanas
929639b06b Merge pull request #863 from jitsi/contact-list-update
Contact list update
2016-09-13 21:21:03 -05:00
damencho
4c72833f5a Adds an option and hide avatars in contact list. 2016-09-13 17:14:05 -05:00
damencho
e3eaac5bef Disables click toggler. 2016-09-13 17:12:10 -05:00
damencho
0683f94edb Skips storing devices (mic and camera) if there is no user selection.
Skips storing device ids in localstorage if the user hasn't selected a device to use, and keeps using system defaults. Removes calls to private library method for setting initial realDeviceIds, as this had been added to the library.
2016-09-07 16:48:57 -05:00
56 changed files with 1988 additions and 1312 deletions

1
.gitignore vendored
View File

@@ -9,3 +9,4 @@ all.css
*css.map
unsupported_browser.css
.remote-sync.json
.sync-config.cson

8
app.js
View File

@@ -8,10 +8,14 @@ import "jquery-ui";
import "strophe";
import "strophe-disco";
import "strophe-caps";
import "tooltip";
import "popover";
import "jQuery-Impromptu";
import "autosize";
import 'aui';
import 'aui-experimental';
import 'aui-css';
import 'aui-experimental-css';
window.toastr = require("toastr");
import URLProcessor from "./modules/config/URLProcessor";

View File

@@ -39,7 +39,7 @@ let connectionIsInterrupted = false;
*/
let DSExternalInstallationInProgress = false;
import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/LargeVideo";
import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/VideoContainer";
/**
* Known custom conference commands.
@@ -293,8 +293,13 @@ function createLocalTracks (options, checkForPermissionPrompt) {
firefox_fake_device: config.firefox_fake_device,
desktopSharingExtensionExternalInstallation:
options.desktopSharingExtensionExternalInstallation
}, checkForPermissionPrompt)
.catch(function (err) {
}, checkForPermissionPrompt).then( (tracks) => {
tracks.forEach((track) => {
track.on(TrackEvents.NO_DATA_FROM_SOURCE,
APP.UI.showTrackNotWorkingDialog.bind(null, track));
});
return tracks;
}).catch(function (err) {
console.error(
'failed to create local tracks', options.devices, err);
return Promise.reject(err);
@@ -357,6 +362,14 @@ class ConferenceConnector {
case ConferenceErrors.PASSWORD_REQUIRED:
APP.UI.markRoomLocked(true);
roomLocker.requirePassword().then(function () {
let pass = roomLocker.password;
// we received that password is required, but user is trying
// anyway to login without a password, mark room as not locked
// in case he succeeds (maybe someone removed the password
// meanwhile), if it is still locked another password required
// will be received and the room again will be marked as locked
if (!pass)
APP.UI.markRoomLocked(false);
room.join(roomLocker.password);
});
break;
@@ -698,6 +711,30 @@ export default {
return room.getLogs();
},
/**
* Download logs, a function that can be called from console while
* debugging.
* @param filename (optional) specify target filename
*/
saveLogs (filename = 'meetlog.json') {
// this can be called from console and will not have reference to this
// that's why we reference the global var
let logs = APP.conference.getLogs();
let data = encodeURIComponent(JSON.stringify(logs, null, ' '));
let elem = document.createElement('a');
elem.download = filename;
elem.href = 'data:application/json;charset=utf-8,\n' + data;
elem.dataset.downloadurl
= ['text/json', elem.download, elem.href].join(':');
elem.dispatchEvent(new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: false
}));
},
/**
* Exposes a Command(s) API on this instance. It is necessitated by (1) the
* desire to keep room private to this instance and (2) the need of other
@@ -857,8 +894,6 @@ export default {
return promise.then(function () {
if (stream) {
stream.on(TrackEvents.TRACK_AUDIO_NOT_WORKING,
APP.UI.showAudioNotWorkingDialog);
return room.addTrack(stream);
}
}).then(() => {
@@ -1389,6 +1424,8 @@ export default {
APP.UI.addListener(UIEvents.PINNED_ENDPOINT, (smallVideo, isPinned) => {
var smallVideoId = smallVideo.getId();
// FIXME why VIDEO_CONTAINER_TYPE instead of checking if
// the participant is on the large video ?
if (smallVideo.getVideoType() === VIDEO_CONTAINER_TYPE
&& !APP.conference.isLocalId(smallVideoId)) {
@@ -1416,7 +1453,7 @@ export default {
.then(([stream]) => {
this.useVideoStream(stream);
console.log('switched local video device');
APP.settings.setCameraDeviceId(cameraDeviceId);
APP.settings.setCameraDeviceId(cameraDeviceId, true);
})
.catch((err) => {
APP.UI.showDeviceErrorDialog(null, err);
@@ -1438,7 +1475,7 @@ export default {
.then(([stream]) => {
this.useAudioStream(stream);
console.log('switched local audio device');
APP.settings.setMicDeviceId(micDeviceId);
APP.settings.setMicDeviceId(micDeviceId, true);
})
.catch((err) => {
APP.UI.showDeviceErrorDialog(err, null);
@@ -1533,13 +1570,13 @@ export default {
// storage and settings menu. This is a workaround until
// getConstraints() method will be implemented in browsers.
if (localAudio) {
localAudio._setRealDeviceIdFromDeviceList(devices);
APP.settings.setMicDeviceId(localAudio.getDeviceId());
APP.settings.setMicDeviceId(
localAudio.getDeviceId(), false);
}
if (localVideo) {
localVideo._setRealDeviceIdFromDeviceList(devices);
APP.settings.setCameraDeviceId(localVideo.getDeviceId());
APP.settings.setCameraDeviceId(
localVideo.getDeviceId(), false);
}
mediaDeviceHelper.setCurrentMediaDevices(devices);
@@ -1640,6 +1677,8 @@ export default {
setRaisedHand(raisedHand) {
if (raisedHand !== this.isHandRaised)
{
APP.UI.onLocalRaiseHandChanged(raisedHand);
this.isHandRaised = raisedHand;
// Advertise the updated status
room.setLocalParticipantProperty("raisedHand", raisedHand);

BIN
css/.DS_Store vendored

Binary file not shown.

View File

@@ -81,15 +81,6 @@ form {
display: block;
}
#downloadlog {
display: none;
position: absolute;
bottom: 5;
left: 5;
overflow: visible;
color: rgba(255,255,255,.50);
}
.active {
background-color: #00ccff;
}
@@ -175,4 +166,15 @@ form {
display: -ms-flexbox !important;
display: -webkit-flex !important;
display: flex !important;
}
}
.tipsy {
z-index: $tooltipsZ;
&-inner {
background-color: $tooltipBg;
}
&-arrow {
border-color: $tooltipBg;
}
}

View File

@@ -104,11 +104,6 @@
color: #a7a7a7;
}
#unreadMessages {
font-size: 8px;
position: absolute;
}
#chat_container .username {
float: left;
padding-left: 5px;

View File

@@ -2,12 +2,11 @@
cursor: default;
> ul#contacts {
position: absolute;
top: 31px;
font-size: 12px;
bottom: 0px;
width: 100%;
margin: 0px;
padding: 0px;
width: 100%;
overflow-y: scroll;
overflow-x: hidden;
}
@@ -20,13 +19,13 @@
#contacts {
>li {
color: $defaultSideBarFontColor;
list-style-type: none;
text-align: left;
white-space: nowrap;
color: #FFF;
font-size: 10pt;
padding: 7px 10px;
margin: 2px;
padding: 6px 10%;
&:hover,
&:active {

View File

@@ -255,9 +255,6 @@
.fa-road:before {
content: "\f018";
}
.fa-download:before {
content: "\f019";
}
.fa-arrow-circle-o-down:before {
content: "\f01a";
}
@@ -842,9 +839,6 @@
.fa-exchange:before {
content: "\f0ec";
}
.fa-cloud-download:before {
content: "\f0ed";
}
.fa-cloud-upload:before {
content: "\f0ee";
}

View File

@@ -16,7 +16,7 @@
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 0.75em;
line-height: 1.22em;
font-size: 1.22em;
/* Better Font Rendering =========== */
@@ -42,9 +42,6 @@
.icon-chat:before {
content: "\e906";
}
.icon-download:before {
content: "\e902";
}
.icon-edit:before {
content: "\e907";
}
@@ -57,12 +54,21 @@
.icon-kick:before {
content: "\e904";
}
.icon-menu-up:before {
content: "\e91f";
}
.icon-menu-down:before {
content: "\e920";
}
.icon-full-screen:before {
content: "\e90b";
}
.icon-exit-full-screen:before {
content: "\e90c";
}
.icon-star:before {
content: "\e916";
}
.icon-star-full:before {
content: "\e90a";
}
@@ -84,6 +90,9 @@
.icon-mic-disabled:before {
content: "\e912";
}
.icon-raised-hand:before {
content: "\e91e";
}
.icon-contactList:before {
content: "\e91b";
}
@@ -96,9 +105,6 @@
.icon-settings:before {
content: "\e915";
}
.icon-star:before {
content: "\e916";
}
.icon-share-desktop:before {
content: "\e917";
}

View File

@@ -36,6 +36,19 @@
}
}
@mixin circle($diameter) {
width: $diameter;
height: $diameter;
border-radius: 50%;
}
@mixin absoluteAligning($sizeX, $sizeY) {
top: 50%;
left: 50%;
position: absolute;
@include transform(translate(-#{$sizeX / 2}, -#{$sizeY / 2}))
}
@mixin transform($func) {
-moz-transform: $func;
-ms-transform: $func;

View File

@@ -12,12 +12,13 @@
background-color: rgba(0,0,0,0.8);
z-index: 800;
overflow: hidden;
letter-spacing: 1px;
/**
* Labels inside the side panel.
*/
label {
color: $defaultSemiDarkColor;
color: $defaultColor;
}
/**
@@ -70,16 +71,16 @@
*/
> div.title,
div.subTitle {
color: $defaultColor !important;
text-align: left;
margin: 10px 0px 10px 0px;
padding: 5px 10px 5px 10px;
}
/**
* Main title size.
*/
> div.title {
color: $defaultColor !important;
text-align: center;
font-size: 16px;
}
@@ -87,10 +88,10 @@
* Subtitle specific properties.
*/
> div.subTitle {
font-size: 12px;
background: $inputSemiBackground !important;
margin-top: 20px !important;
margin-bottom: 8px !important;
font-size: 11px;
font-weight: 500;
color: $defaultSideBarFontColor !important;
margin-left: 10%;
}
/**

View File

@@ -56,11 +56,11 @@
}
#extendedToolbar {
display: flex;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-box;
display: -webkit-flex;
display: flex;
width: $defaultToolbarSize;
height: 100%;
top: 0px;
@@ -83,14 +83,6 @@
display: none;
}
#numberOfParticipants {
position: absolute;
top: 5px;
line-height: 13px;
font-weight: bold;
font-size: 11px;
}
#mainToolbar a.button:last-child::after {
content: none;
}
@@ -118,6 +110,10 @@
cursor: default;
}
.button.toggled {
color: $toolbarButtonToggled;
}
a.button.unclickable:hover,
a.button.unclickable:active,
a.button.unclickable.selected{
@@ -129,6 +125,7 @@ a.button:hover,
a.button:active,
a.button.selected {
cursor: pointer;
text-decoration: none;
// sum opacity with background layer should give us 0.8
background: $toolbarSelectBackground;
}
@@ -144,6 +141,36 @@ a.button>#avatar {
margin-top: auto;
}
/**
* Round badge.
*/
.badge-round {
background-color: $toolbarBadgeBackground;
color: $toolbarBadgeColor;
font-size: 9px;
line-height: 13px;
font-weight: 700;
text-align: center;
border-radius: 50%;
min-width: 13px;
overflow: hidden;
text-overflow: ellipsis;
box-sizing: border-box;
vertical-align: middle;
// Do not inherit the font-family from the toolbar button, because it's an
// icon style.
font-family: $baseFontFamily;
}
/**
* Toolbar specific round badge.
*/
.toolbar .badge-round {
position: absolute;
right: 9px;
bottom: 9px;
}
/**
* START of slide in animation for extended toolbar.
*/

View File

@@ -1,5 +1,4 @@
/**
<<<<<<< HEAD
* Style variables
*/
$baseFontFamily: 'open_sanslight', 'Helvetica Neue', Helvetica, Arial, sans-serif;
@@ -11,20 +10,47 @@ $hangupFontSize: 2em;
*/
$defaultToolbarSize: 50px;
// Video layout.
$thumbnailIndicatorSize: 23px;
$thumbnailIndicatorBorder: 0px;
$thumbnailVideoMargin: 2px;
$thumbnailToolbarHeight: 25px;
/**
* Color variables.
*/
$defaultColor: #F1F1F1;
$defaultSemiDarkColor: #ACACAC;
$defaultSideBarFontColor: #44A5FF;
$defaultDarkColor: #4F4F4F;
$defaultBackground: #474747;
$tooltipBg: rgba(0,0,0, 0.7);
// Toolbar
$toolbarSelectBackground: rgba(0, 0, 0, .6);
$toolbarButtonToggled: #44A5FF;
$toolbarBadgeBackground: #165ECC;
$toolbarBadgeColor: #FFFFFF;
// Main controls
$inputBackground: rgba(132, 132, 132, .5);
$inputSemiBackground: rgba(132, 132, 132, .8);
$inputLightBackground: #EBEBEB;
$inputBorderColor: #EBEBEB;
$buttonBackground: #44A5FF;
// Video layout.
$videoThumbnailHovered: #44A5FF;
$videoThumbnailSelected: #165ECC;
$participantNameColor: #fff;
$thumbnailPictogramColor: #fff;
$dominantSpeakerBg: #165ecc;
$raiseHandBg: #D6D61E;
$rateStarDefault: #ccc;
$rateStarActivity: #165ecc;
$rateStarLabelColor: #333;
/**
* Misc.
*/
@@ -34,9 +60,17 @@ $defaultWatermarkLink: '../images/watermark.png';
/**
* Z-indexes. TODO: Replace this by a function.
*/
$tooltipsZ: 901;
$toolbarZ: 900;
$overlayZ: 800;
$rateStarDefault: #ccc;
$rateStarActivity: #f6c342;
$rateStarLabelColor: #333;
/**
* Font Colors TODO: Change colors when general dialogs are implemented.
*/
$defaultFontColor: #777;
$defaultLightFontColor: #F1F1F1;
$defaultDarkFontColor: #000;
$buttonFontColor: #777;
$buttonHoverFontColor: #287ade;
$linkFontColor: #489afe;
$linkHoverFontColor: #287ade;

View File

@@ -13,17 +13,17 @@
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
flex-direction: row;
flex-direction: row-reverse;
flex-wrap: nowrap;
justify-content: flex-end;
justify-content: flex-start;
position:absolute;
text-align:right;
height:196px;
padding: 18px;
padding: 10px 10px 10px 5px;
bottom: 0;
left: 0;
right: 20px;
right: 0;
width:auto;
border:1px solid transparent;
z-index: 5;
@@ -43,12 +43,33 @@
#remoteVideos .videocontainer {
display: none;
position: relative;
background-color: black;
background-size: contain;
border-radius:1px;
border: 1px solid #212425;
margin: 0 $thumbnailVideoMargin;
border: 1px solid $defaultDarkColor;
}
/**
* The toolbar of the video thumbnail.
*/
.videocontainer__toolbar {
position: absolute;
bottom: 0;
left: 0;
z-index: 1;
width: 100%;
box-sizing: border-box; // Includes the padding in the 100% width.
height: $thumbnailToolbarHeight;
max-height: 100%;
background-color: rgba(0, 0, 0, 0.5);
padding: 0 5px 0 5px;
}
/**
* Focused video thumbnail.
*/
#remoteVideos .videocontainer.videoContainerFocused {
cursor: hand;
transition-duration: 0.5s;
@@ -56,20 +77,12 @@
-webkit-animation-name: greyPulse;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: 1;
border: 1px solid $videoThumbnailSelected;
}
#remoteVideos .videocontainer:hover {
border: 1px solid #c1c1c1;
}
#remoteVideos .videocontainer.videoContainerFocused {
box-shadow: inset 0 0 28px #006d91;
border: 1px solid #006d91;
}
#remoteVideos .videocontainer:hover,
#remoteVideos .videocontainer.videoContainerFocused:hover {
box-shadow: inset 0 0 5px #c1c1c1, 0 0 10px #c1c1c1, inset 0 0 60px #006d91;
border: 1px solid #c1c1c1;
border: 1px solid $videoThumbnailHovered;
}
#localVideoWrapper {
@@ -141,47 +154,36 @@
z-index: 0;
}
#remoteVideos .videocontainer>span.focusindicator,
#remoteVideos .videocontainer>div.remotevideomenu {
position: absolute;
color: #FFFFFF;
top: 0;
left: 0;
padding: 5px 0px;
width: 25px;
font-size: 11pt;
text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.7);
border: 0px;
z-index: 2;
text-align: center;
}
#remoteVideos .videocontainer>span.focusindicator {
/**
* Positions video thumbnail display name and editor.
*/
.videocontainer .displayname,
.videocontainer .editdisplayname {
display: inline-block;
}
#remoteVideos .videocontainer>div.remotevideomenu {
display: block;
}
.videocontainer>span.displayname,
.videocontainer>input.displayname {
display: none;
position: absolute;
color: #FFFFFF;
background: rgba(0,0,0,.7);
left: 30%;
width: 40%;
color: $participantNameColor;
text-align: center;
text-overflow: ellipsis;
width: 70%;
height: 20%;
left: 15%;
top: 40%;
padding: 5px;
font-size: 11pt;
font-size: 12px;
font-weight: 100;
letter-spacing: 1px;
overflow: hidden;
white-space: nowrap;
line-height: $thumbnailToolbarHeight;
z-index: 2;
border-radius:3px;
}
/**
* Positions video thumbnail display name editor.
*/
.videocontainer .editdisplayname {
outline: none;
border: none;
background: none;
box-shadow: none;
padding: 0;
}
.videocontainer>span.status {
@@ -257,16 +259,16 @@
}
#localVideoContainer>span.status:hover,
#localVideoContainer>span.displayname:hover {
#localVideoContainer .displayname:hover {
cursor: text;
}
.videocontainer>span.status,
.videocontainer>span.displayname {
.videocontainer .displayname {
pointer-events: none;
}
.videocontainer>input.displayname {
.videocontainer .editdisplayname {
height: auto;
}
@@ -287,53 +289,63 @@
z-index: 2;
}
.videocontainer>span.audioMuted {
display: inline-block;
position: absolute;
color: #FFFFFF;
top: 0;
padding: 8px 5px;
width: 25px;
/**
* Video thumbnail toolbar icon.
*/
.videocontainer .toolbar-icon {
font-size: 8pt;
text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.7);
border: 0px;
z-index: 3;
text-align: center;
text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.7);
color: #FFFFFF;
width: 12px;
line-height: $thumbnailToolbarHeight;
height: $thumbnailToolbarHeight;
padding: 0;
border: 0;
margin: 0px 5px 0px 0px;
float: left;
}
.videocontainer>span.videoMuted {
display: inline-block;
position: absolute;
color: #FFFFFF;
top: 0;
right: 0;
padding: 8px 5px;
width: 25px;
font-size: 8pt;
text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.7);
border: 0px;
z-index: 3;
/**
* Toolbar icon internal i elements (font icons).
*/
.toolbar-icon>i {
line-height: $thumbnailToolbarHeight;
}
/**
* Toolbar icons positioned on the right.
*/
.toolbar-icon.right {
float: right;
margin: 0px 0px 0px 5px;
}
.videocontainer>span.indicator {
bottom: 0px;
position: absolute;
top: 0px;
left: 0px;
width: 25px;
height: 25px;
@include circle($thumbnailIndicatorSize);
box-sizing: border-box;
line-height: $thumbnailIndicatorSize - 2*$thumbnailIndicatorBorder;
z-index: 3;
text-align: center;
border-radius: 50%;
background: #21B9FC;
margin: 5px;
background: $dominantSpeakerBg;
margin: 7px;
display: inline-block;
position: absolute;
color: #FFFFFF;
font-size: 11pt;
border: 0px;
color: $thumbnailPictogramColor;
font-size: 8pt;
border: $thumbnailIndicatorBorder solid $thumbnailPictogramColor;
}
.videocontainer>#raisehandindicator {
background: $raiseHandBg;
}
#indicatoricon {
padding-top: 5px;
width: $thumbnailIndicatorSize - 2*$thumbnailIndicatorBorder;
height: $thumbnailIndicatorSize - 2*$thumbnailIndicatorBorder;
line-height: $thumbnailIndicatorSize - 2*$thumbnailIndicatorBorder;
}
#reloadPresentation {
@@ -395,10 +407,8 @@
}
.userAvatar {
height: 100%;
position: absolute;
left: 0;
border-radius: 2px;
@include circle(60px);
@include absoluteAligning(60px, 60px);
}
.sharedVideoAvatar {

View File

@@ -22,6 +22,8 @@
@import 'toastr';
@import 'base';
@import 'overlay/overlay';
@import 'modals/dialog';
@import 'modals/feedback/feedback';
@import 'videolayout_default';
@import 'jquery-impromptu';
@import 'modaldialog';
@@ -38,7 +40,6 @@
@import 'toolbars';
@import 'side_toolbar_container';
@import 'device_settings_dialog';
@import 'feedback';
@import 'jquery.contextMenu';
@import 'keyboard-shortcuts';

37
css/modals/_dialog.scss Normal file
View File

@@ -0,0 +1,37 @@
.dialog{
visibility: visible;
height: auto;
p {
color: $defaultDarkFontColor;
}
.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{
padding-top: 0;
}
.aui-button {
height: 36px;
padding-top: 12px;
border: none;
background-color: transparent!important;
border-left: solid 1px #e4e4e4;
font-weight: 700;
&_close {
color: $defaultFontColor;
}
&_submit {
color: $linkFontColor;
&:hover {
color: $linkHoverFontColor;
}
}
}
}

View File

@@ -33,6 +33,8 @@
}
.shake-rotate {
display: inline-block;
-webkit-animation-duration: .4s;
animation-duration: .4s;
-webkit-animation-iteration-count: infinite;
@@ -43,65 +45,65 @@
animation-timing-function: ease-in-out
}
.text-center {
text-align: center;
}
.feedbackDetails textarea {
resize: vertical;
min-height: 100px;
}
.feedback-rating {
line-height: 1.2;
padding: 20px 0;
.feedback {
h2 {
font-weight: 400;
font-size: 24px;
line-height: 1.2;
padding: auto;
margin: auto;
border: none;
}
p {
margin-top: 10px;
margin-left: 0px;
margin-bottom: 0px;
margin-right: 0px;
font-weight: 400;
font-size: 14px;
}
.star-label {
font-size: 16px;
color: $rateStarLabelColor;
&__content {
text-align: center;
}
&__footer {
.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;
color: #287ade;
outline: 0;
}
}
&__rating {
line-height: 1.2;
padding: 20px 0;
.fa {
top: -6px;
}
};
&.rated:hover .fa {
top: 0;
p {
margin: 10px 0 0;
}
.fa {
.star-label {
font-size: 16px;
color: $rateStarLabelColor;
}
.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;
> i:before {
content: "\e90a";
}
};
}
}
&__details {
text-align: left;
textarea {
min-height: 100px;
width: 100%;
}
}
}

View File

@@ -9,6 +9,11 @@
background: linear-gradient(transparent, #000);
opacity: 0.8;
&.solidBG {
background: $defaultBackground;
opacity: 1;
}
&__content {
position: absolute;
width: 400px;
@@ -33,4 +38,4 @@
color: #333;
}
}
}
}

Binary file not shown.

View File

@@ -41,4 +41,7 @@
<glyph unicode="&#xe91b;" glyph-name="contactList" d="M704 746c-46 0-86-38-86-84s40-86 86-86 86 40 86 86-40 84-86 84zM704 512c-82 0-150 68-150 150s68 148 150 148 150-66 150-148-68-150-150-150zM320 746c-46 0-86-38-86-84s40-86 86-86 86 40 86 86-40 84-86 84zM320 512c-82 0-150 68-150 150s68 148 150 148 150-66 150-148-68-150-150-150zM918 278v52c0 24-110 76-214 76-46 0-90-12-128-24 14-16 22-32 22-52v-52h320zM534 278v52c0 24-110 76-214 76s-214-52-214-76v-52h428zM704 470c92 0 278-48 278-140v-116h-940v116c0 92 186 140 278 140 52 0 130-16 192-44 62 28 140 44 192 44z" />
<glyph unicode="&#xe91c;" glyph-name="toggle-filmstrip" d="M896 896h-768c-46.933 0-85.333-38.4-85.333-85.333v-597.333c0-46.933 38.4-85.333 85.333-85.333h768c46.933 0 85.333 38.4 85.333 85.333v597.333c0 46.933-38.4 85.333-85.333 85.333zM896 213.333h-768v128h768v-128z" />
<glyph unicode="&#xe91d;" glyph-name="feedback" d="M42.667 128h170.667v512h-170.667v-512zM981.333 597.333c0 46.933-38.4 85.333-85.333 85.333h-269.227l40.533 194.987 1.28 13.653c0 17.493-7.253 33.707-18.773 45.227l-45.227 44.8-280.747-281.173c-15.787-15.36-25.173-36.693-25.173-60.16v-426.667c0-46.933 38.4-85.333 85.333-85.333h384c35.413 0 65.707 21.333 78.507 52.053l128.853 300.8c3.84 9.813 5.973 20.053 5.973 31.147v81.493l-0.427 0.427 0.427 3.413z" />
<glyph unicode="&#xe91e;" glyph-name="raised-hand" d="M982 790v-620c0-94-78-170-172-170h-310c-46 0-90 18-122 50l-336 342s54 52 56 52c10 8 22 12 34 12 10 0 18-2 26-6 2 0 184-104 184-104v508c0 36 28 64 64 64s64-28 64-64v-300h42v406c0 36 28 64 64 64s64-28 64-64v-406h42v364c0 36 28 64 64 64s64-28 64-64v-364h44v236c0 36 28 64 64 64s64-28 64-64z" />
<glyph unicode="&#xe91f;" glyph-name="menu-up" d="M512 682l256-256-60-60-196 196-196-196-60 60z" />
<glyph unicode="&#xe920;" glyph-name="menu-down" d="M708 658l60-60-256-256-256 256 60 60 196-196z" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -69,8 +69,10 @@
"tags": [
"account_circle"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 11,
"order": 60,
@@ -93,8 +95,10 @@
"tags": [
"autorenew"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 68,
"order": 84,
@@ -117,8 +121,10 @@
"tags": [
"call_end"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 122,
"order": 63,
@@ -141,8 +147,10 @@
"tags": [
"chat_bubble_outline"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 148,
"order": 61,
@@ -165,8 +173,10 @@
"tags": [
"cloud_download"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 164,
"order": 99,
@@ -189,8 +199,10 @@
"tags": [
"mode_edit"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 185,
"order": 89,
@@ -213,8 +225,10 @@
"tags": [
"description"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 206,
"order": 85,
@@ -237,8 +251,10 @@
"tags": [
"dialer_sip"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 215,
"order": 95,
@@ -261,8 +277,10 @@
"tags": [
"eject"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 242,
"order": 98,
@@ -275,6 +293,58 @@
"setId": 2,
"iconIdx": 243
},
{
"icon": {
"paths": [
"M512 342l256 256-60 60-196-196-196 196-60-60z"
],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"expand_less"
],
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 256,
"order": 106,
"ligatures": "expand_less",
"prevSize": 32,
"code": 59679,
"name": "menu-up"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 257
},
{
"icon": {
"paths": [
"M708 366l60 60-256 256-256-256 60-60 196 196z"
],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"expand_more"
],
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 257,
"order": 107,
"ligatures": "expand_more",
"prevSize": 32,
"code": 59680,
"name": "menu-down"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 258
},
{
"icon": {
"paths": [
@@ -285,8 +355,10 @@
"tags": [
"fullscreen"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 350,
"order": 94,
@@ -309,8 +381,10 @@
"tags": [
"fullscreen_exit"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 351,
"order": 92,
@@ -333,8 +407,10 @@
"tags": [
"star"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 363,
"order": 101,
@@ -357,8 +433,10 @@
"tags": [
"lock_open"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 473,
"order": 66,
@@ -381,8 +459,10 @@
"tags": [
"lock_outline"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 474,
"order": 65,
@@ -405,8 +485,10 @@
"tags": [
"sync"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 482,
"order": 67,
@@ -429,8 +511,10 @@
"tags": [
"mic"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 492,
"order": 68,
@@ -453,8 +537,10 @@
"tags": [
"mic_none"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 493,
"order": 69,
@@ -477,8 +563,10 @@
"tags": [
"mic_off"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 494,
"order": 70,
@@ -491,6 +579,32 @@
"setId": 2,
"iconIdx": 495
},
{
"icon": {
"paths": [
"M982 234v620c0 94-78 170-172 170h-310c-46 0-90-18-122-50l-336-342s54-52 56-52c10-8 22-12 34-12 10 0 18 2 26 6 2 0 184 104 184 104v-508c0-36 28-64 64-64s64 28 64 64v300h42v-406c0-36 28-64 64-64s64 28 64 64v406h42v-364c0-36 28-64 64-64s64 28 64 64v364h44v-236c0-36 28-64 64-64s64 28 64 64z"
],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"pan_tool"
],
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 539,
"order": 105,
"ligatures": "pan_tool",
"prevSize": 32,
"code": 59678,
"name": "raised-hand"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 540
},
{
"icon": {
"paths": [
@@ -501,8 +615,10 @@
"tags": [
"people_outline"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 549,
"order": 100,
@@ -525,8 +641,10 @@
"tags": [
"person_add"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 559,
"order": 87,
@@ -549,8 +667,10 @@
"tags": [
"play_circle_outline"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 590,
"order": 82,
@@ -573,8 +693,10 @@
"tags": [
"settings"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 665,
"order": 81,
@@ -597,8 +719,10 @@
"tags": [
"star_border"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 717,
"order": 76,
@@ -621,8 +745,10 @@
"tags": [
"tv"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 783,
"order": 93,
@@ -645,8 +771,10 @@
"tags": [
"videocam"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 798,
"order": 77,
@@ -669,8 +797,10 @@
"tags": [
"videocam_off"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 799,
"order": 78,
@@ -693,8 +823,10 @@
"tags": [
"volume_up"
],
"grid": 0
"grid": 0,
"attrs": []
},
"attrs": [],
"properties": {
"id": 821,
"order": 79,

View File

@@ -120,26 +120,32 @@
</li>
</ul>
</span>
<a class="button icon-contactList" id="toolbar_contact_list" data-container="body" data-toggle="popover" data-placement="right" shortcut="contactlistpopover" data-i18n="[content]bottomtoolbar.contactlist" content="Open / close contact list">
<span id="numberOfParticipants"></span>
<a class="button icon-contactList" id="toolbar_contact_list" shortcut="contactlistpopover">
<span class="badge-round">
<span id="numberOfParticipants"></span>
</span>
</a>
<a class="button icon-chat" id="toolbar_button_chat" data-container="body" data-toggle="popover" shortcut="toggleChatPopover" data-placement="right" data-i18n="[content]toolbar.chat" content="Open / close chat">
<span id="unreadMessages"></span>
<!--a class="button icon-link" id="toolbar_button_link"></a-->
<a class="button icon-chat" id="toolbar_button_chat" shortcut="toggleChatPopover">
<span class="badge-round">
<span id="unreadMessages"></span>
</span>
</a>
<a class="button" id="toolbar_button_record" data-container="body" data-toggle="popover" data-placement="right" style="display: none"></a>
<a class="button icon-security" id="toolbar_button_security" data-container="body" data-toggle="popover" data-placement="right" data-i18n="[content]toolbar.lock" content="Lock / unlock room"></a>
<a class="button icon-share-doc" id="toolbar_button_etherpad" data-container="body" data-toggle="popover" data-placement="right" content="Shared document" data-i18n="[content]toolbar.etherpad"></a>
<a class="button icon-shared-video" id="toolbar_button_sharedvideo" data-container="body" data-toggle="popover" data-placement="right" content="Share a YouTube video" data-i18n="[content]toolbar.sharedvideo" style="display: none">
<a class="button" id="toolbar_button_record" style="display: none"></a>
<a class="button icon-security" id="toolbar_button_security"></a>
<a class="button icon-share-doc" id="toolbar_button_etherpad"></a>
<a class="button icon-shared-video" id="toolbar_button_sharedvideo" style="display: none">
<ul id="sharedVideoMutedPopup" class="loginmenu extendedToolbarPopup">
<li data-i18n="[html]toolbar.sharedVideoMutedPopup"></li>
</ul>
</a>
<a class="button icon-telephone" id="toolbar_button_sip" data-container="body" data-toggle="popover" data-placement="right" content="Call SIP number" data-i18n="[content]toolbar.sip" style="display: none"></a>
<a class="button icon-dialpad" id="toolbar_button_dialpad" data-container="body" data-toggle="popover" data-placement="right" content="Open dialpad" data-i18n="[content]toolbar.dialpad" style="display: none"></a>
<a class="button icon-settings" id="toolbar_button_settings" data-container="body" data-toggle="popover" data-placement="right" content="Settings" data-i18n="[content]toolbar.Settings"></a>
<a class="button icon-full-screen" id="toolbar_button_fullScreen" data-container="body" data-toggle="popover" data-placement="right" shortcut="toggleFullscreenPopover" data-i18n="[content]toolbar.fullscreen" content="Enter / Exit Full Screen"></a>
<a class="button icon-toggle-filmstrip" id="toolbar_film_strip" data-container="body" data-toggle="popover" shortcut="filmstripPopover" data-placement="right" data-i18n="[content]toolbar.filmstrip" content="Show / hide videos"></a>
<a class="button icon-feedback" id="feedbackButton" data-container="body" data-toggle="popover" data-placement="right" data-i18n="[content]feedback"></a>
<a class="button icon-telephone" id="toolbar_button_sip" style="display: none"></a>
<a class="button icon-dialpad" id="toolbar_button_dialpad" style="display: none"></a>
<a class="button icon-settings" id="toolbar_button_settings"></a>
<a class="button icon-raised-hand" id="toolbar_button_raisehand" shortcut="raiseHandPopover"></a>
<a class="button icon-full-screen" id="toolbar_button_fullScreen" shortcut="toggleFullscreenPopover"></a>
<a class="button icon-toggle-filmstrip" id="toolbar_film_strip" data-container="body" shortcut="filmstripPopover"></a>
<a class="button icon-feedback" id="feedbackButton"></a>
<div id="sideToolbarContainer">
<div id="profile_container" class="sideToolbarContainer__inner">
<div class="title" data-i18n="profile.title"></div>
@@ -206,7 +212,6 @@
<input type="checkbox" id="followMeCheckBox">
<label class="followMeLabel" for="followMeCheckBox" data-i18n="settings.followMe"></label>
</div>
<a id="downloadlog" data-container="body" data-toggle="popover" data-placement="right" data-i18n="[data-content]downloadlogs" ><i class="icon-download"></i></a>
</div>
</div>
</div>
@@ -236,12 +241,13 @@
</div>
<div id="remoteVideos">
<span id="localVideoContainer" class="videocontainer">
<span id="localVideoContainer" class="videocontainer videocontainer_small">
<span id="localVideoWrapper">
<!--<video id="localVideo" autoplay muted></video> - is now per stream generated -->
</span>
<audio id="localAudio" autoplay muted></audio>
<span class="focusindicator"></span>
<div class="videocontainer__toolbar"></div>
</span>
<audio id="userJoined" src="sounds/joined.wav" preload="auto"></audio>
<audio id="userLeft" src="sounds/left.wav" preload="auto"></audio>
@@ -255,5 +261,6 @@
</ul>
</div>
</div>
<div id="aui-feedback-dialog" class="dialog feedback aui-layer aui-dialog2 aui-dialog2-medium" style="display: none;"></div>
</body>
</html>

View File

@@ -20,18 +20,25 @@ var interfaceConfig = {
// the toolbar buttons line is intentionally left in one line, to be able
// to easily override values or remove them using regex
MAIN_TOOLBAR_BUTTONS: ['microphone', 'camera', 'desktop', 'invite', 'hangup'], // jshint ignore:line
TOOLBAR_BUTTONS: ['profile', 'authentication', 'microphone', 'camera', 'desktop', 'recording', 'security', 'invite', 'chat', 'etherpad', 'sharedvideo', 'fullscreen', 'sip', 'dialpad', 'settings', 'hangup', 'filmstrip', 'contacts'], // jshint ignore:line
TOOLBAR_BUTTONS: ['profile', 'authentication', 'microphone', 'camera', 'desktop', 'recording', 'security', 'raisehand', 'chat', 'etherpad', 'sharedvideo', 'fullscreen', 'sip', 'dialpad', 'settings', 'hangup', 'filmstrip', 'contacts'], // jshint ignore:line
SETTINGS_SECTIONS: ['language', 'devices', 'moderator'],
// Determines how the video would fit the screen. 'both' would fit the whole
// screen, 'height' would fit the original video height to the height of the
// screen, 'width' would fit the original video width to the width of the
// screen respecting ratio.
VIDEO_LAYOUT_FIT: 'both',
SHOW_CONTACTLIST_AVATARS: false,
/**
* Whether to only show the filmstrip (and hide the toolbar).
*/
filmStripOnly: false,
RANDOM_AVATAR_URL_PREFIX: false,
RANDOM_AVATAR_URL_SUFFIX: false,
FILM_STRIP_MAX_HEIGHT: 120
};
FILM_STRIP_MAX_HEIGHT: 120,
LOCAL_THUMBNAIL_RATIO_WIDTH: 16,
LOCAL_THUMBNAIL_RATIO_HEIGHT: 9,
REMOTE_THUMBNAIL_RATIO_WIDTH: 1,
REMOTE_THUMBNAIL_RATIO_HEIGHT: 1,
// Enables feedback star animation.
ENABLE_FEEDBACK_ANIMATION: false
};

View File

@@ -1,11 +1,9 @@
{
"contactlist": "ON CALL (__participants__)",
"contactlist": "On Call",
"connectionsettings": "Connection Settings",
"poweredby": "powered by",
"downloadlogs": "Download logs",
"feedback": "Give us your feedback",
"roomUrlDefaultMsg": "Your conference is currently being created...",
"participant": "Participant",
"me": "me",
"speaker": "Speaker",
"raisedHand": "Would like to speak",
@@ -94,7 +92,8 @@
"unableToUnmutePopup": "You cannot un-mute while the shared video is on.",
"cameraDisabled": "Camera is not available",
"micDisabled": "Microphone is not available",
"filmstrip": "Show / hide videos"
"filmstrip": "Show / hide videos",
"raiseHand": "Raise hand to speak"
},
"bottomtoolbar": {
"chat": "Open / close chat",
@@ -110,21 +109,21 @@
},
"settings":
{
"title": "SETTINGS",
"title": "Settings",
"update": "Update",
"name": "Name",
"startAudioMuted": "Everyone starts muted",
"startVideoMuted": "Everyone starts hidden",
"selectCamera": "Select camera",
"selectMic": "Select microphone",
"selectAudioOutput": "Select audio output",
"selectCamera": "Camera",
"selectMic": "Microphone",
"selectAudioOutput": "Audio output",
"followMe": "Everyone follows me",
"noDevice": "None",
"noPermission": "Permission to use device is not granted",
"cameraAndMic": "Camera and microphone",
"moderator": "MODERATOR",
"password": "SET PASSWORD",
"audioVideo": "AUDIO / VIDEO",
"audioVideo": "AUDIO AND VIDEO",
"setPasswordLabel": "Lock your room with a password."
},
"profile": {
@@ -136,7 +135,7 @@
{
"editnickname": "Click to edit your<br/>display name",
"moderator": "The owner of<br/>this conference",
"videomute": "Participant has<br/>stopped the camera.",
"videomute": "Participant has<br/>stopped the camera",
"mute": "Participant is muted",
"kick": "Kick out",
"muted": "Muted",
@@ -197,8 +196,8 @@
"lockMessage": "Failed to lock the conference.",
"warning": "Warning",
"passwordNotSupported": "Room passwords are currently not supported.",
"sorry": "Sorry",
"internalError": "Internal application error [setRemoteDescription]",
"internalErrorTitle": "Internal error",
"internalError": "Oups! Something went wrong. The following error occurred: [setRemoteDescription]",
"unableToSwitch": "Unable to switch video stream.",
"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)",
@@ -246,13 +245,14 @@
"password": "Enter password",
"userPassword": "user password",
"token": "token",
"tokenAuthFailed": "Failed to authenticate with XMPP server: invalid token",
"tokenAuthFailedTitle": "Authentication problem",
"tokenAuthFailed": "Sorry, you're not allowed to join this call.",
"displayNameRequired": "Please enter your display name",
"extensionRequired": "Extension required:",
"firefoxExtensionPrompt": "You need to install a Firefox extension in order to use screen sharing. Please try again after you <a href='__url__'>get it from here</a>!",
"rateExperience": "Please rate your meeting experience.",
"feedbackHelp": "Your feedback will help us to improve our video experience.",
"feedbackQuestion": "How was your call?",
"feedbackQuestion": "Tell us about your call!",
"thankYou": "Thank you for using __appName__!",
"sorryFeedback": "We're sorry to hear that. Would you like to tell us more?",
"liveStreaming": "Live Streaming",
@@ -277,6 +277,7 @@
"micNotFoundError": "Microphone was not found.",
"micConstraintFailedError": "Yor microphone does not satisfy some of required constraints.",
"micNotSendingData": "We are unable to access your microphone. Please select another device from the settings menu or try to restart the application.",
"cameraNotSendingData": "We are unable to access your camera. Please check if another application is using this device, select another device from the settings menu or try to restart the application.",
"goToStore": "Go to the webstore",
"externalInstallationTitle": "Extension required",
"externalInstallationMsg": "You need to install our desktop sharing extension."

View File

@@ -1,321 +0,0 @@
/* global $, APP, config, interfaceConfig, JitsiMeetJS */
import UIEvents from "../../service/UI/UIEvents";
/**
* Constructs the html for the overall feedback window.
*
* @returns {string} the constructed html string
*/
var constructOverallFeedbackHtml = function() {
var feedbackQuestion = (Feedback.feedbackScore < 0)
? '<br/><br/>' + APP.translation
.translateString("dialog.feedbackQuestion")
: '';
var message = '<div class="feedback"><div>' +
'<div class="feedbackTitle">' +
APP.translation.translateString("dialog.thankYou",
{appName:interfaceConfig.APP_NAME}) +
'</div>' +
feedbackQuestion +
'</div><br/><br/>' +
'<div id="stars">' +
'<a><i class="icon-star icon-star-full"></i></a>' +
'<a><i class="icon-star icon-star-full"></i></a>' +
'<a><i class="icon-star icon-star-full"></i></a>' +
'<a><i class="icon-star icon-star-full"></i></a>' +
'<a><i class="icon-star icon-star-full"></i></a>' +
'</div></div>';
return message;
};
/**
* Constructs the html for the detailed feedback window.
*
* @returns {string} the contructed html string
*/
var constructDetailedFeedbackHtml = function() {
// Construct the html, which will be served as a dialog message.
var message = '<div class="feedback">' +
'<div class="feedbackTitle">' +
APP.translation.translateString("dialog.sorryFeedback") +
'</div><br/><br/>' +
'<div class="feedbackDetails">' +
'<textarea id="feedbackTextArea" rows="10" cols="50" autofocus>' +
'</textarea>' +
'</div></div>';
return message;
};
var createRateFeedbackHTML = function () {
var rate = APP.translation.translateString('dialog.rateExperience'),
help = APP.translation.translateString('dialog.feedbackHelp');
return `
<div class="feedback-rating text-center">
<h2>${ rate }</h2>
<p class="star-label">&nbsp;</p>
<div id="stars" class="feedback-stars">
<a class="star-btn">
<i class="fa fa-star shake-rotate"></i>
</a>
<a class="star-btn">
<i class="fa fa-star shake-rotate"></i>
</a>
<a class="star-btn">
<i class="fa fa-star shake-rotate"></i>
</a>
<a class="star-btn">
<i class="fa fa-star shake-rotate"></i>
</a>
<a class="star-btn">
<i class="fa fa-star shake-rotate"></i>
</a>
</div>
<p>&nbsp;</p>
<p>${ help }</p>
</div>
`;
};
/**
* The callback function corresponding to the openFeedbackWindow parameter.
*
* @type {function}
*/
var feedbackWindowCallback = null;
/**
* Shows / hides the feedback button.
* @private
*/
function _toggleFeedbackIcon() {
$('#feedbackButtonDiv').toggleClass("hidden");
}
/**
* Shows / hides the feedback button.
* @param {show} set to {true} to show the feedback button or to {false}
* to hide it
* @private
*/
function _showFeedbackButton (show) {
var feedbackButton = $("#feedbackButtonDiv");
if (show)
feedbackButton.css("display", "block");
else
feedbackButton.css("display", "none");
}
/**
* Defines all methods in connection to the Feedback window.
*
* @type {{feedbackScore: number, openFeedbackWindow: Function,
* toggleStars: Function, hoverStars: Function, unhoverStars: Function}}
*/
var Feedback = {
/**
* The feedback score. -1 indicates no score has been given for now.
*/
feedbackScore: -1,
/**
* Initialise the Feedback functionality.
* @param emitter the EventEmitter to associate with the Feedback.
*/
init: function (emitter) {
// CallStats is the way we send feedback, so we don't have to initialise
// if callstats isn't enabled.
if (!APP.conference.isCallstatsEnabled())
return;
// If enabled property is still undefined, i.e. it hasn't been set from
// some other module already, we set it to true by default.
if (typeof this.enabled == "undefined")
this.enabled = true;
_showFeedbackButton(this.enabled);
$("#feedbackButton").click(function (event) {
Feedback.openFeedbackWindow();
});
// Show / hide the feedback button whenever the film strip is
// shown / hidden.
emitter.addListener(UIEvents.TOGGLE_FILM_STRIP, function () {
_toggleFeedbackIcon();
});
},
/**
* Enables/ disabled the feedback feature.
*/
enableFeedback: function (enable) {
if (this.enabled !== enable)
_showFeedbackButton(enable);
this.enabled = enable;
},
/**
* Indicates if the feedback functionality is enabled.
*
* @return true if the feedback functionality is enabled, false otherwise.
*/
isEnabled: function() {
return this.enabled && APP.conference.isCallstatsEnabled();
},
/**
* Returns true if the feedback window is currently visible and false
* otherwise.
* @return {boolean} true if the feedback window is visible, false
* otherwise
*/
isVisible: function() {
return $(".feedback").is(":visible");
},
/**
* Opens the feedback window.
*/
openFeedbackWindow: function (callback) {
feedbackWindowCallback = callback;
// Add all mouse and click listeners.
var onLoadFunction = function (event) {
$('#stars >a').each(function(index) {
// On star mouse over.
$(this).get(0).onmouseover = function(){
Feedback.hoverStars(index);
};
// On star mouse leave.
$(this).get(0).onmouseleave = function(){
Feedback.unhoverStars(index);
};
// On star click.
$(this).get(0).onclick = function(){
Feedback.toggleStars(index);
Feedback.feedbackScore = index+1;
// If the feedback is less than 3 stars we're going to
// ask the user for more information.
if (Feedback.feedbackScore > 3) {
APP.conference.sendFeedback(Feedback.feedbackScore, "");
if (feedbackWindowCallback)
feedbackWindowCallback();
else
APP.UI.messageHandler.closeDialog();
}
else {
feedbackDialog.goToState('detailed_feedback');
}
};
// Init stars to correspond to previously entered feedback.
if (Feedback.feedbackScore > 0
&& index < Feedback.feedbackScore) {
Feedback.hoverStars(index);
Feedback.toggleStars(index);
}
});
};
// Defines the different states of the feedback window.
var states = {
overall_feedback: {
html: createRateFeedbackHTML(),
persistent: false,
buttons: {},
closeText: '',
focus: "div[id='stars']",
position: {width: 500}
},
detailed_feedback: {
html: constructDetailedFeedbackHtml(),
buttons: {"Submit": true, "Cancel": false},
closeText: '',
focus: "textarea[id='feedbackTextArea']",
position: {width: 500},
submit: function(e,v,m,f) {
e.preventDefault();
if (v) {
var feedbackDetails
= document.getElementById("feedbackTextArea").value;
if (feedbackDetails && feedbackDetails.length > 0) {
APP.conference.sendFeedback( Feedback.feedbackScore,
feedbackDetails);
}
if (feedbackWindowCallback)
feedbackWindowCallback();
else
APP.UI.messageHandler.closeDialog();
} else {
// User cancelled
if (feedbackWindowCallback)
feedbackWindowCallback();
else
APP.UI.messageHandler.closeDialog();
}
}
}
};
// Create the feedback dialog.
var feedbackDialog
= APP.UI.messageHandler.openDialogWithStates(
states,
{ persistent: false,
buttons: {},
closeText: '',
loaded: onLoadFunction,
position: {width: 500}}, null);
JitsiMeetJS.analytics.sendEvent('feedback.open');
},
/**
* Toggles the appropriate css class for the given number of stars, to
* indicate that those stars have been clicked/selected.
*
* @param starCount the number of stars, for which to toggle the css class
*/
toggleStars: function (starCount)
{
$('#stars >a >i').each(function(index) {
if (index <= starCount) {
$(this).removeClass("icon-star");
}
else
$(this).addClass("icon-star");
});
},
/**
* Toggles the appropriate css class for the given number of stars, to
* indicate that those stars have been hovered.
*
* @param starCount the number of stars, for which to toggle the css class
*/
hoverStars: function (starCount)
{
$('#stars >a >i').each(function(index) {
if (index <= starCount)
$(this).addClass("starHover");
});
},
/**
* Toggles the appropriate css class for the given number of stars, to
* indicate that those stars have been un-hovered.
*
* @param starCount the number of stars, for which to toggle the css class
*/
unhoverStars: function (starCount)
{
$('#stars >a >i').each(function(index) {
if (index <= starCount && $(this).hasClass("icon-star"))
$(this).removeClass("starHover");
});
}
};
// Exports the Feedback class.
module.exports = Feedback;

View File

@@ -29,7 +29,7 @@ var EventEmitter = require("events");
UI.messageHandler = require("./util/MessageHandler");
var messageHandler = UI.messageHandler;
var JitsiPopover = require("./util/JitsiPopover");
var Feedback = require("./Feedback");
var Feedback = require("./feedback/Feedback");
import FollowMe from "../FollowMe";
@@ -60,6 +60,8 @@ JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.NOT_FOUND]
= "dialog.cameraNotFoundError";
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.CONSTRAINT_FAILED]
= "dialog.cameraConstraintFailedError";
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.NO_DATA_FROM_SOURCE]
= "dialog.cameraNotSendingData";
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.GENERAL]
= "dialog.micUnknownError";
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.PERMISSION_DENIED]
@@ -68,6 +70,8 @@ JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.NOT_FOUND]
= "dialog.micNotFoundError";
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.CONSTRAINT_FAILED]
= "dialog.micConstraintFailedError";
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.NO_DATA_FROM_SOURCE]
= "dialog.micNotSendingData";
/**
* Prompt user for nickname.
@@ -292,7 +296,7 @@ UI.initConference = function () {
}
// Add myself to the contact list.
ContactList.addContact(id);
ContactList.addContact(id, true);
//update default button states before showing the toolbar
//if local role changes buttons state will be again updated
@@ -339,6 +343,22 @@ UI.handleToggleFilmStrip = () => {
VideoLayout.resizeVideoArea(true, false);
};
/**
* Sets tooltip defaults.
*
* @private
*/
function _setTooltipDefaults() {
$.fn.tooltip.defaults = {
opacity: 1, //defaults to 1
offset: 1,
delayIn: 0, //defaults to 500
hoverable: true,
hideOnClick: true,
aria: true
};
}
/**
* Setup some UI event listeners.
*/
@@ -431,6 +451,9 @@ UI.start = function () {
// Set the defaults for prompt dialogs.
$.prompt.setDefaults({persistent: false});
// Set the defaults for tooltips.
_setTooltipDefaults();
registerListeners();
ToolbarToggler.init();
@@ -463,20 +486,10 @@ UI.start = function () {
$('#noticeText').text(config.noticeMessage);
$('#notice').css({display: 'block'});
}
$("#downloadlog").click(function (event) {
let logs = APP.conference.getLogs();
let data = encodeURIComponent(JSON.stringify(logs, null, ' '));
let elem = event.target.parentNode;
elem.download = 'meetlog.json';
elem.href = 'data:application/json;charset=utf-8,\n' + data;
});
} else {
$("#mainToolbarContainer").css("display", "none");
$("#downloadlog").css("display", "none");
FilmStrip.setupFilmStripOnly();
messageHandler.enableNotifications(false);
$('body').popover("disable");
JitsiPopover.enabled = false;
}
@@ -1108,7 +1121,7 @@ UI.requestFeedback = function () {
if (Feedback.isEnabled()) {
// If the user has already entered feedback, we'll show the
// window and immidiately start the conference dispose timeout.
if (Feedback.feedbackScore > 0) {
if (Feedback.getFeedbackScore() > 0) {
Feedback.openFeedbackWindow();
resolve();
@@ -1134,11 +1147,13 @@ UI.updateRecordingState = function (state) {
};
UI.notifyTokenAuthFailed = function () {
messageHandler.showError("dialog.error", "dialog.tokenAuthFailed");
messageHandler.showError( "dialog.tokenAuthFailedTitle",
"dialog.tokenAuthFailed");
};
UI.notifyInternalError = function () {
messageHandler.showError("dialog.sorry", "dialog.internalError");
messageHandler.showError( "dialog.internalErrorTitle",
"dialog.internalError");
};
UI.notifyFocusDisconnected = function (focus, retrySec) {
@@ -1193,6 +1208,16 @@ UI.onStartMutedChanged = function (startAudioMuted, startVideoMuted) {
SettingsMenu.updateStartMutedBox(startAudioMuted, startVideoMuted);
};
/**
* Notifies interested listeners that the raise hand property has changed.
*
* @param {boolean} isRaisedHand indicates the current state of the
* "raised hand"
*/
UI.onLocalRaiseHandChanged = function (isRaisedHand) {
eventEmitter.emit(UIEvents.LOCAL_RAISE_HAND_CHANGED, isRaisedHand);
};
/**
* Update list of available physical devices.
* @param {object[]} devices new list of available devices
@@ -1415,12 +1440,13 @@ UI.showDeviceErrorDialog = function (micError, cameraError) {
/**
* Shows error dialog that informs the user that no data is received from the
* microphone.
* device.
*/
UI.showAudioNotWorkingDialog = function () {
UI.showTrackNotWorkingDialog = function (stream) {
messageHandler.openMessageDialog(
"dialog.error",
"dialog.micNotSendingData",
stream.isAudioTrack()? "dialog.micNotSendingData" :
"dialog.cameraNotSendingData",
null,
null);
};

View File

@@ -10,7 +10,7 @@ let ASDrawContext = null;
let audioLevelCanvasCache = {};
let dominantSpeakerAudioElement = null;
function initDominantSpeakerAudioLevels(dominantSpeakerAvatarSize) {
function _initDominantSpeakerAudioLevels(dominantSpeakerAvatarSize) {
let ASRadius = dominantSpeakerAvatarSize / 2;
let ASCenter = (dominantSpeakerAvatarSize + ASRadius) / 2;
@@ -28,7 +28,9 @@ function initDominantSpeakerAudioLevels(dominantSpeakerAvatarSize) {
/**
* Resizes the given audio level canvas to match the given thumbnail size.
*/
function resizeAudioLevelCanvas(audioLevelCanvas, thumbnailWidth, thumbnailHeight) {
function _resizeAudioLevelCanvas( audioLevelCanvas,
thumbnailWidth,
thumbnailHeight) {
audioLevelCanvas.width = thumbnailWidth + interfaceConfig.CANVAS_EXTRA;
audioLevelCanvas.height = thumbnailHeight + interfaceConfig.CANVAS_EXTRA;
}
@@ -138,18 +140,18 @@ const AudioLevels = {
dominantSpeakerAudioElement.height = dominantSpeakerHeight;
let dominantSpeakerAvatar = $("#dominantSpeakerAvatar");
initDominantSpeakerAudioLevels(dominantSpeakerAvatar.width());
_initDominantSpeakerAudioLevels(dominantSpeakerAvatar.width());
},
/**
* Updates the audio level canvas for the given id. If the canvas
* didn't exist we create it.
*/
updateAudioLevelCanvas (id, thumbWidth, thumbHeight) {
let videoSpanId = 'localVideoContainer';
if (id) {
videoSpanId = `participant_${id}`;
}
createAudioLevelCanvas (id, thumbWidth, thumbHeight) {
let videoSpanId = (id === "local")
? "localVideoContainer"
: `participant_${id}`;
let videoSpan = document.getElementById(videoSpanId);
@@ -172,13 +174,13 @@ const AudioLevels = {
= `-${interfaceConfig.CANVAS_EXTRA/2}px`;
audioLevelCanvas.style.left
= `-${interfaceConfig.CANVAS_EXTRA/2}px`;
resizeAudioLevelCanvas(audioLevelCanvas, thumbWidth, thumbHeight);
_resizeAudioLevelCanvas(audioLevelCanvas, thumbWidth, thumbHeight);
videoSpan.appendChild(audioLevelCanvas);
} else {
audioLevelCanvas = audioLevelCanvas.get(0);
resizeAudioLevelCanvas(audioLevelCanvas, thumbWidth, thumbHeight);
_resizeAudioLevelCanvas(audioLevelCanvas, thumbWidth, thumbHeight);
}
},
@@ -242,19 +244,29 @@ const AudioLevels = {
ASDrawContext.fill();
},
updateCanvasSize (thumbWidth, thumbHeight) {
let canvasWidth = thumbWidth + interfaceConfig.CANVAS_EXTRA;
let canvasHeight = thumbHeight + interfaceConfig.CANVAS_EXTRA;
updateCanvasSize (localVideo, remoteVideo) {
let localCanvasWidth
= localVideo.thumbWidth + interfaceConfig.CANVAS_EXTRA;
let localCanvasHeight
= localVideo.thumbHeight + interfaceConfig.CANVAS_EXTRA;
let remoteCanvasWidth
= remoteVideo.thumbWidth + interfaceConfig.CANVAS_EXTRA;
let remoteCanvasHeight
= remoteVideo.thumbHeight + interfaceConfig.CANVAS_EXTRA;
FilmStrip.getThumbs().children('canvas').each(function () {
$(this).attr('width', canvasWidth);
$(this).attr('height', canvasHeight);
let { remoteThumbs, localThumb } = FilmStrip.getThumbs();
remoteThumbs.children('canvas').each(function () {
$(this).attr('width', remoteCanvasWidth);
$(this).attr('height', remoteCanvasHeight);
});
Object.keys(audioLevelCanvasCache).forEach(function (id) {
audioLevelCanvasCache[id].width = canvasWidth;
audioLevelCanvasCache[id].height = canvasHeight;
});
if(localThumb) {
localThumb.children('canvas').each(function () {
$(this).attr('width', localCanvasWidth);
$(this).attr('height', localCanvasHeight);
});
}
}
};

View File

@@ -185,6 +185,10 @@ export default function createRoomLocker (room) {
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

@@ -0,0 +1,105 @@
/* global $, APP, config, interfaceConfig, JitsiMeetJS */
import UIEvents from "../../../service/UI/UIEvents";
import FeedabckWindow from "./FeedbackWindow";
/**
* Shows / hides the feedback button.
* @private
*/
function _toggleFeedbackIcon() {
$('#feedbackButtonDiv').toggleClass("hidden");
}
/**
* Shows / hides the feedback button.
* @param {show} set to {true} to show the feedback button or to {false}
* to hide it
* @private
*/
function _showFeedbackButton (show) {
var feedbackButton = $("#feedbackButtonDiv");
if (show)
feedbackButton.css("display", "block");
else
feedbackButton.css("display", "none");
}
/**
* Defines all methods in connection to the Feedback window.
*
* @type {{openFeedbackWindow: Function}}
*/
var Feedback = {
/**
* Initialise the Feedback functionality.
* @param emitter the EventEmitter to associate with the Feedback.
*/
init: function (emitter) {
// CallStats is the way we send feedback, so we don't have to initialise
// if callstats isn't enabled.
if (!APP.conference.isCallstatsEnabled())
return;
// If enabled property is still undefined, i.e. it hasn't been set from
// some other module already, we set it to true by default.
if (typeof this.enabled == "undefined")
this.enabled = true;
_showFeedbackButton(this.enabled);
this.window = new FeedabckWindow({});
$("#feedbackButton").click(Feedback.openFeedbackWindow);
// Show / hide the feedback button whenever the film strip is
// shown / hidden.
emitter.addListener(UIEvents.TOGGLE_FILM_STRIP, function () {
_toggleFeedbackIcon();
});
},
/**
* Enables/ disabled the feedback feature.
*/
enableFeedback: function (enable) {
if (this.enabled !== enable)
_showFeedbackButton(enable);
this.enabled = enable;
},
/**
* Indicates if the feedback functionality is enabled.
*
* @return true if the feedback functionality is enabled, false otherwise.
*/
isEnabled: function() {
return this.enabled && APP.conference.isCallstatsEnabled();
},
/**
* Returns true if the feedback window is currently visible and false
* otherwise.
* @return {boolean} true if the feedback window is visible, false
* otherwise
*/
isVisible: function() {
return $(".feedback").is(":visible");
},
/**
* Opens the feedback window.
*/
openFeedbackWindow: function (callback) {
Feedback.window.show(callback);
JitsiMeetJS.analytics.sendEvent('feedback.open');
},
getFeedbackScore: function() {
return Feedback.window.feedbackScore;
}
};
module.exports = Feedback;

View File

@@ -0,0 +1,223 @@
/* global $, APP, interfaceConfig, AJS */
/* jshint -W101 */
const selector = '#aui-feedback-dialog';
/**
* Toggles the appropriate css class for the given number of stars, to
* indicate that those stars have been clicked/selected.
*
* @param starCount the number of stars, for which to toggle the css class
*/
let toggleStars = function(starCount) {
$('#stars > a').each(function(index, el) {
if (index <= starCount) {
el.classList.add("starHover");
} else
el.classList.remove("starHover");
});
};
/**
* Constructs the html for the detailed feedback window.
*
* @returns {string} the contructed html string
*/
let constructDetailedFeedbackHtml = function() {
return `
<div class="aui-dialog2-content feedback__content">
<div class="feedback__details">
<p>${APP.translation.translateString("dialog.sorryFeedback")}</p>
<br/><br/>
<textarea id="feedbackTextArea" rows="10" cols="50" autofocus></textarea>
</div>
</div>
<footer class="aui-dialog2-footer feedback__footer">
<div class="aui-dialog2-footer-actions">
<button id="dialog-close-button" class="aui-button aui-button_close">Close</button>
<button id="dialog-submit-button" class="aui-button aui-button_submit">Submit</button>
</div>
</footer>
`;
};
/**
* Constructs the html for the rated feedback window.
*
* @returns {string} the contructed html string
*/
let createRateFeedbackHTML = function (Feedback) {
let rateExperience
= APP.translation.translateString('dialog.rateExperience'),
feedbackHelp = APP.translation.translateString('dialog.feedbackHelp'),
feedbackQuestion = (Feedback.feedbackScore < 0)
? `<p><br/>${APP.translation.translateString('dialog.feedbackQuestion')}</p>`
: '';
let starClassName = (interfaceConfig.ENABLE_FEEDBACK_ANIMATION)
? "icon-star shake-rotate"
: "icon-star";
return `
<div class="aui-dialog2-content feedback__content">
${feedbackQuestion}
<form action="javascript:false;" onsubmit="return false;">
<div class="feedback__rating">
<h2>${ rateExperience }</h2>
<p class="star-label">&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>
</form>
</div>
`;
};
/**
* Callback for Rate Feedback
*
* @param Feedback
*/
let onLoadRateFunction = function (Feedback) {
$('#stars > a').each((index, el) => {
el.onmouseover = function(){
toggleStars(index);
};
el.onmouseleave = function(){
toggleStars(Feedback.feedbackScore - 1);
};
el.onclick = function(){
Feedback.feedbackScore = index + 1;
// If the feedback is less than 3 stars we're going to
// ask the user for more information.
if (Feedback.feedbackScore > 3) {
APP.conference.sendFeedback(Feedback.feedbackScore, "");
Feedback.hide();
} else {
Feedback.setState('detailed_feedback');
}
};
});
// Init stars to correspond to previously entered feedback.
if (Feedback.feedbackScore > 0) {
toggleStars(Feedback.feedbackScore - 1);
}
};
/**
* Callback for Detailed Feedback
*
* @param Feedback
*/
let onLoadDetailedFunction = function(Feedback) {
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();
};
/**
* @class Dialog
*
*/
export default class Dialog {
constructor(options) {
this.feedbackScore = -1;
this.onCloseCallback = null;
this.states = {
rate_feedback: {
getHtml: createRateFeedbackHTML,
onLoad: onLoadRateFunction
},
detailed_feedback: {
getHtml: constructDetailedFeedbackHtml,
onLoad: onLoadDetailedFunction
}
};
this.state = options.state || 'rate_feedback';
this.window = AJS.dialog2(selector, {
closeOnOutsideClick: true
});
this.$el = this.window.$el;
this.setState();
}
setState(state) {
let newState = state || this.state;
let htmlStr = this.states[newState].getHtml(this);
this.$el.html(htmlStr);
this.states[newState].onLoad(this);
}
show(cb) {
this.setState('rate_feedback');
if (typeof cb == 'function') {
this.onCloseCallback = cb;
}
this.window.show();
}
hide() {
this.window.hide();
if (this.onCloseCallback) {
this.onCloseCallback();
this.onCloseCallback = null;
}
}
onFeedbackSubmitted() {
let message = this.$el.find('textarea').val();
let self = this;
if (message && message.length > 0) {
APP.conference.sendFeedback(
self.feedbackScore,
message);
}
this.hide();
}
}

View File

@@ -17,7 +17,7 @@
import UIEvents from "../../../service/UI/UIEvents";
import UIUtil from '../util/UIUtil';
import VideoLayout from '../videolayout/VideoLayout';
import Feedback from '../Feedback.js';
import Feedback from '../feedback/Feedback.js';
import Toolbar from '../toolbars/Toolbar';
/**
@@ -270,6 +270,9 @@ var Recording = {
initRecordingButton(recordingType) {
let selector = $('#toolbar_button_record');
let button = selector.get(0);
UIUtil.setTooltip(button, 'liveStreaming.buttonTooltip', 'right');
if (recordingType === 'jibri') {
this.baseClass = "fa fa-play-circle";
this.recordingTitle = "dialog.liveStreaming";

View File

@@ -1,5 +1,21 @@
/* global $ */
/* global $, APP */
/* jshint -W101 */
import UIEvents from "../../../service/UI/UIEvents";
/**
* Store the current ring overlay instance.
* Note: We want to have only 1 instance at a time.
*/
let overlay = null;
/**
* Handler for UIEvents.LARGE_VIDEO_AVATAR_DISPLAYED event.
* @param {boolean} shown indicates whether the avatar on the large video is
* currently displayed or not.
*/
function onAvatarDisplayed(shown) {
overlay._changeBackground(shown);
}
/**
* Shows ring overlay
@@ -11,25 +27,46 @@ class RingOverlay {
constructor(callee) {
this._containerId = 'ringOverlay';
this._audioContainerId = 'ringOverlayRinging';
this.isRinging = true;
this.callee = callee;
this.render();
this.audio = document.getElementById(this._audioContainerId);
this.audio.play();
this._setAudioTimeout();
this._timeout = setTimeout(() => {
this.destroy();
this.render();
}, 30000);
}
/**
* Chagnes the background of the ring overlay.
* @param {boolean} solid - if true the new background will be the solid
* one, otherwise the background will be default one.
* NOTE: The method just toggles solidBG css class.
*/
_changeBackground(solid) {
const container = $("#" + this._containerId);
if(solid) {
container.addClass("solidBG");
} else {
container.removeClass("solidBG");
}
}
/**
* Builds and appends the ring overlay to the html document
*/
_getHtmlStr(callee) {
let callingLabel = this.isRinging? "<p>Calling...</p>" : "";
let callerStateLabel = this.isRinging? "" : " isn't available";
return `
<div id="${this._containerId}" class='ringing' >
<div class='ringing__content'>
<p>Calling...</p>
${callingLabel}
<img class='ringing__avatar' src="${callee.getAvatarUrl()}" />
<div class="ringing__caller-info">
<p>${callee.getName()}</p>
<p>${callee.getName()}${callerStateLabel}</p>
</div>
</div>
<audio id="${this._audioContainerId}" src="/sounds/ring.ogg" />
@@ -49,10 +86,7 @@ class RingOverlay {
* related to the ring overlay.
*/
destroy() {
if (this.interval) {
clearInterval(this.interval);
}
this._stopAudio();
this._detach();
}
@@ -64,6 +98,16 @@ class RingOverlay {
$(`#${this._containerId}`).remove();
}
_stopAudio() {
this.isRinging = false;
if (this.interval) {
clearInterval(this.interval);
}
if(this._timeout) {
clearTimeout(this._timeout);
}
}
/**
* Sets the interval that is going to play the ringing sound.
*/
@@ -74,12 +118,6 @@ class RingOverlay {
}
}
/**
* Store the current ring overlay instance.
* Note: We want to have only 1 instance at a time.
*/
let overlay = null;
export default {
/**
* Shows the ring overlay for the passed callee.
@@ -92,6 +130,8 @@ export default {
}
overlay = new RingOverlay(callee);
APP.UI.addListener(UIEvents.LARGE_VIDEO_AVATAR_DISPLAYED,
onAvatarDisplayed);
},
/**
@@ -104,6 +144,8 @@ export default {
}
overlay.destroy();
overlay = null;
APP.UI.removeListener(UIEvents.LARGE_VIDEO_AVATAR_DISPLAYED,
onAvatarDisplayed);
return true;
},

View File

@@ -3,20 +3,24 @@
import {processReplacements, linkify} from './Replacement';
import CommandsProcessor from './Commands';
import ToolbarToggler from '../../toolbars/ToolbarToggler';
import VideoLayout from "../../videolayout/VideoLayout";
import UIUtil from '../../util/UIUtil';
import UIEvents from '../../../../service/UI/UIEvents';
var smileys = require("./smileys.json").smileys;
import { smileys } from './smileys';
var notificationInterval = false;
var unreadMessages = 0;
/**
* The container id, which is and the element id.
*/
var CHAT_CONTAINER_ID = "chat_container";
/**
* Shows/hides a visual notification, indicating that a message has arrived.
* Updates visual notification, indicating that a message has arrived.
*/
function setVisualNotification(show) {
function updateVisualNotification() {
var unreadMsgElement = document.getElementById('unreadMessages');
var glower = $('#toolbar_button_chat');
@@ -37,28 +41,12 @@ function setVisualNotification(show) {
'style',
'top:' + topIndent +
'; left:' + leftIndent + ';');
if (!glower.hasClass('icon-chat-simple')) {
glower.removeClass('icon-chat');
glower.addClass('icon-chat-simple');
}
}
else {
unreadMsgElement.innerHTML = '';
glower.removeClass('icon-chat-simple');
glower.addClass('icon-chat');
}
if (show && !notificationInterval) {
notificationInterval = window.setInterval(function () {
glower.toggleClass('active');
}, 800);
}
else if (!show && notificationInterval) {
window.clearInterval(notificationInterval);
notificationInterval = false;
glower.removeClass('active');
}
$(unreadMsgElement).parent()[unreadMessages > 0 ? 'show' : 'hide']();
}
@@ -131,7 +119,7 @@ function addSmileys() {
*/
function resizeChatConversation() {
var msgareaHeight = $('#usermsg').outerHeight();
var chatspace = $('#chat_container');
var chatspace = $('#' + CHAT_CONTAINER_ID);
var width = chatspace.width();
var chat = $('#chatconversation');
var smileys = $('#smileysarea');
@@ -187,13 +175,30 @@ var Chat = {
};
usermsg.autosize({callback: onTextAreaResize});
$("#chat_container").bind("shown",
function () {
eventEmitter.on(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
function(containerId, isVisible) {
if (containerId !== CHAT_CONTAINER_ID || !isVisible)
return;
unreadMessages = 0;
setVisualNotification(false);
updateVisualNotification();
// Undock the toolbar when the chat is shown and if we're in a
// video mode.
if (VideoLayout.isLargeVideoVisible()) {
ToolbarToggler.dockToolbar(false);
}
// if we are in conversation mode focus on the text input
// if we are not, focus on the display name input
if (APP.settings.getDisplayName())
$('#usermsg').focus();
else
$('#nickinput').focus();
});
addSmileys();
updateVisualNotification();
},
/**
@@ -210,7 +215,7 @@ var Chat = {
if (!Chat.isVisible()) {
unreadMessages++;
UIUtil.playSoundNotification('chatNotification');
setVisualNotification(true);
updateVisualNotification();
}
}
@@ -271,12 +276,18 @@ var Chat = {
/**
* Sets the chat conversation mode.
* Conversation mode is the normal chat mode, non conversation mode is
* where we ask user to input its display name.
* @param {boolean} isConversationMode if chat should be in
* conversation mode or not.
*/
setChatConversationMode (isConversationMode) {
$('#chat_container')
$('#' + CHAT_CONTAINER_ID)
.toggleClass('is-conversation-mode', isConversationMode);
// this is needed when we transition from no conversation mode to
// conversation mode. When user enters his nickname and hits enter,
// to focus on the write area.
if (isConversationMode) {
$('#usermsg').focus();
}
@@ -286,7 +297,7 @@ var Chat = {
* Resizes the chat area.
*/
resizeChat (width, height) {
$('#chat_container').width(width).height(height);
$('#' + CHAT_CONTAINER_ID).width(width).height(height);
resizeChatConversation();
},
@@ -296,7 +307,7 @@ var Chat = {
*/
isVisible () {
return UIUtil.isVisible(
document.getElementById("chat_container"));
document.getElementById(CHAT_CONTAINER_ID));
},
/**
* Shows and hides the window with the smileys

View File

@@ -1,5 +1,5 @@
/* jshint -W101 */
var Smileys = require("./smileys.json");
import { regexes } from './smileys';
/**
* Processes links and smileys in "body"
@@ -29,7 +29,7 @@ export function linkify(inputText) {
replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
replacedText = replacedText.replace(replacePattern2, '$1<a href="http://$2" target="_blank">$2</a>');
//Change email addresses to mailto:: links.
//Change email addresses to mailto: links.
replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>');
@@ -44,10 +44,9 @@ function smilify(body) {
return body;
}
var regexs = Smileys.regexs;
for(var smiley in regexs) {
if(regexs.hasOwnProperty(smiley)) {
body = body.replace(regexs[smiley],
for(var smiley in regexes) {
if(regexes.hasOwnProperty(smiley)) {
body = body.replace(regexes[smiley],
'<img class="smiley" src="images/smileys/' + smiley + '.svg">');
}
}

View File

@@ -0,0 +1,47 @@
export const smileys = {
smiley1: ":)",
smiley2: ":(",
smiley3: ":D",
smiley4: "(y)",
smiley5: " :P",
smiley6: "(wave)",
smiley7: "(blush)",
smiley8: "(chuckle)",
smiley9: "(shocked)",
smiley10: ":*",
smiley11: "(n)",
smiley12: "(search)",
smiley13: " <3",
smiley14: "(oops)",
smiley15: "(angry)",
smiley16: "(angel)",
smiley17: "(sick)",
smiley18: ";(",
smiley19: "(bomb)",
smiley20: "(clap)",
smiley21: " ;)"
};
export const regexes = {
smiley2: /(:-\(\(|:-\(|:\(\(|:\(|\(sad\))/gi,
smiley3: /(:-\)\)|:\)\)|\(lol\)|:-D|:D)/gi,
smiley1: /(:-\)|:\))/gi,
smiley4: /(\(y\)|\(Y\)|\(ok\))/gi,
smiley5: /(:-P|:P|:-p|:p)/gi,
smiley6: /(\(wave\))/gi,
smiley7: /(\(blush\))/gi,
smiley8: /(\(chuckle\))/gi,
smiley9: /(:-0|\(shocked\))/gi,
smiley10: /(:-\*|:\*|\(kiss\))/gi,
smiley11: /(\(n\))/gi,
smiley12: /(\(search\))/g,
smiley13: /(<3|&lt;3|&amp;lt;3|\(L\)|\(l\)|\(H\)|\(h\))/gi,
smiley14: /(\(oops\))/gi,
smiley15: /(\(angry\))/gi,
smiley16: /(\(angel\))/gi,
smiley17: /(\(sick\))/gi,
smiley18: /(;-\(\(|;\(\(|;-\(|;\(|:"\(|:"-\(|:~-\(|:~\(|\(upset\))/gi,
smiley19: /(\(bomb\))/gi,
smiley20: /(\(clap\))/gi,
smiley21: /(;-\)|;\)|;-\)\)|;\)\)|;-D|;D|\(wink\))/gi
};

View File

@@ -1,48 +0,0 @@
{
"smileys": {
"smiley1": ":)",
"smiley2": ":(",
"smiley3": ":D",
"smiley4": "(y)",
"smiley5": " :P",
"smiley6": "(wave)",
"smiley7": "(blush)",
"smiley8": "(chuckle)",
"smiley9": "(shocked)",
"smiley10": ":*",
"smiley11": "(n)",
"smiley12": "(search)",
"smiley13": " <3",
"smiley14": "(oops)",
"smiley15": "(angry)",
"smiley16": "(angel)",
"smiley17": "(sick)",
"smiley18": ";(",
"smiley19": "(bomb)",
"smiley20": "(clap)",
"smiley21": " ;)"
},
"regexs": {
"smiley2": /(:-\(\(|:-\(|:\(\(|:\(|\(sad\))/gi,
"smiley3": /(:-\)\)|:\)\)|\(lol\)|:-D|:D)/gi,
"smiley1": /(:-\)|:\))/gi,
"smiley4": /(\(y\)|\(Y\)|\(ok\))/gi,
"smiley5": /(:-P|:P|:-p|:p)/gi,
"smiley6": /(\(wave\))/gi,
"smiley7": /(\(blush\))/gi,
"smiley8": /(\(chuckle\))/gi,
"smiley9": /(:-0|\(shocked\))/gi,
"smiley10": /(:-\*|:\*|\(kiss\))/gi,
"smiley11": /(\(n\))/gi,
"smiley12": /(\(search\))/g,
"smiley13": /(<3|&lt;3|&amp;lt;3|\(L\)|\(l\)|\(H\)|\(h\))/gi,
"smiley14": /(\(oops\))/gi,
"smiley15": /(\(angry\))/gi,
"smiley16": /(\(angel\))/gi,
"smiley17": /(\(sick\))/gi,
"smiley18": /(;-\(\(|;\(\(|;-\(|;\(|:"\(|:"-\(|:~-\(|:~\(|\(upset\))/gi,
"smiley19": /(\(bomb\))/gi,
"smiley20": /(\(clap\))/gi,
"smiley21": /(;-\)|;\)|;-\)\)|;\)\)|;-D|;D|\(wink\))/gi
}
}

View File

@@ -1,4 +1,4 @@
/* global $, APP */
/* global $, APP, interfaceConfig */
import Avatar from '../../avatar/Avatar';
import UIEvents from '../../../../service/UI/UIEvents';
import UIUtil from '../../util/UIUtil';
@@ -21,12 +21,13 @@ function updateNumberOfParticipants(delta) {
}
let buttonIndicatorText = (numberOfContacts === 1) ? '' : numberOfContacts;
$("#numberOfParticipants").text(buttonIndicatorText);
$("#numberOfParticipants")
.text(buttonIndicatorText)
.parent()[numberOfContacts > 1 ? 'show' : 'hide']();
$("#contacts_container>div.title").text(
APP.translation.translateString(
"contactlist", {participants: numberOfContacts}
));
APP.translation.translateString("contactlist")
+ ' (' + numberOfContacts + ')');
}
/**
@@ -96,9 +97,9 @@ var ContactList = {
/**
* Adds a contact for the given id.
*
* @param isLocal is an id for the local user.
*/
addContact (id) {
addContact (id, isLocal) {
let contactlist = $('#contacts');
let newContact = document.createElement('li');
@@ -110,8 +111,13 @@ var ContactList = {
}
};
newContact.appendChild(createAvatar(id));
newContact.appendChild(createDisplayNameParagraph("participant"));
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);

View File

@@ -74,10 +74,7 @@ export default {
}
});
// Only show the subtitle if this isn't the only setting section.
if (interfaceConfig.SETTINGS_SECTIONS.length > 1)
UIUtil.showElement("deviceOptionsTitle");
UIUtil.showElement("deviceOptionsTitle");
UIUtil.showElement("devicesOptions");
}
@@ -150,8 +147,7 @@ export default {
showStartMutedOptions (show) {
if (show && UIUtil.isSettingEnabled('moderator')) {
// Only show the subtitle if this isn't the only setting section.
if (!$("#moderatorOptionsTitle").is(":visible")
&& interfaceConfig.SETTINGS_SECTIONS.length > 1)
if (!$("#moderatorOptionsTitle").is(":visible"))
UIUtil.showElement("moderatorOptionsTitle");
UIUtil.showElement("startMutedOptions");

View File

@@ -7,7 +7,6 @@ import SideContainerToggler from "../side_pannels/SideContainerToggler";
let roomUrl = null;
let emitter = null;
/**
* Opens the invite link dialog.
*/
@@ -168,14 +167,20 @@ const buttonHandlers = {
},
"toolbar_film_strip": function () {
JitsiMeetJS.analytics.sendEvent(
'bottomtoolbar.filmstrip.toggled');
'toolbar.filmstrip.toggled');
emitter.emit(UIEvents.TOGGLE_FILM_STRIP);
},
"toolbar_button_raisehand": function () {
JitsiMeetJS.analytics.sendEvent(
'toolbar.raiseHand.clicked');
APP.conference.maybeToggleRaisedHand();
}
};
const defaultToolbarButtons = {
'microphone': {
id: 'toolbar_button_mute',
tooltipKey: 'toolbar.mute',
className: "button icon-microphone",
shortcut: 'M',
shortcutAttr: 'mutePopover',
@@ -201,6 +206,7 @@ const defaultToolbarButtons = {
},
'camera': {
id: 'toolbar_button_camera',
tooltipKey: 'toolbar.videomute',
className: "button icon-camera",
shortcut: 'V',
shortcutAttr: 'toggleVideoPopover',
@@ -214,6 +220,7 @@ const defaultToolbarButtons = {
},
'desktop': {
id: 'toolbar_button_desktopsharing',
tooltipKey: 'toolbar.sharescreen',
className: 'button icon-share-desktop',
shortcut: 'D',
shortcutAttr: 'toggleDesktopSharingPopover',
@@ -226,16 +233,19 @@ const defaultToolbarButtons = {
i18n: '[content]toolbar.sharescreen'
},
'security': {
id: 'toolbar_button_security'
id: 'toolbar_button_security',
tooltipKey: 'toolbar.lock'
},
'invite': {
id: 'toolbar_button_link',
tooltipKey: 'toolbar.invite',
className: 'button icon-link',
content: 'Invite others',
i18n: '[content]toolbar.invite'
},
'chat': {
id: 'toolbar_button_chat',
tooltipKey: 'toolbar.chat',
shortcut: 'C',
shortcutAttr: 'toggleChatPopover',
shortcutFunc: function() {
@@ -247,17 +257,21 @@ const defaultToolbarButtons = {
},
'contacts': {
id: 'toolbar_contact_list',
tooltipKey: 'bottomtoolbar.contactlist',
sideContainerId: 'contacts_container'
},
'profile': {
id: 'toolbar_button_profile',
tooltipKey: 'profile.setDisplayNameLabel',
sideContainerId: 'profile_container'
},
'etherpad': {
id: 'toolbar_button_etherpad'
id: 'toolbar_button_etherpad',
tooltipKey: 'toolbar.etherpad',
},
'fullscreen': {
id: 'toolbar_button_fullScreen',
tooltipKey: 'toolbar.fullscreen',
className: "button icon-full-screen",
shortcut: 'F',
shortcutAttr: 'toggleFullscreenPopover',
@@ -271,16 +285,19 @@ const defaultToolbarButtons = {
},
'settings': {
id: 'toolbar_button_settings',
tooltipKey: 'toolbar.Settings',
sideContainerId: "settings_container"
},
'hangup': {
id: 'toolbar_button_hangup',
tooltipKey: 'toolbar.hangup',
className: "button icon-hangup",
content: "Hang Up",
i18n: "[content]toolbar.hangup"
},
'filmstrip': {
id: 'toolbar_film_strip',
tooltipKey: 'toolbar.filmstrip',
shortcut: "F",
shortcutAttr: "filmstripPopover",
shortcutFunc: function() {
@@ -288,6 +305,20 @@ const defaultToolbarButtons = {
APP.UI.toggleFilmStrip();
},
shortcutDescription: "keyboardShortcuts.toggleFilmstrip"
},
'raisehand': {
id: "toolbar_button_raisehand",
tooltipKey: 'toolbar.raiseHand',
className: "button icon-raised-hand",
shortcut: "R",
shortcutAttr: "raiseHandPopover",
shortcutFunc: function() {
JitsiMeetJS.analytics.sendEvent("shortcut.raisehand.clicked");
APP.conference.maybeToggleRaisedHand();
},
shortcutDescription: "keyboardShortcuts.raiseHand",
content: "Raise Hand",
i18n: "[content]toolbar.raiseHand"
}
};
@@ -323,14 +354,28 @@ const Toolbar = {
this.toolbarSelector = $("#mainToolbarContainer");
this.extendedToolbarSelector = $("#extendedToolbar");
this._initMainToolbarButtons();
// First hide all disabled buttons in the extended toolbar.
// TODO: Make the extended toolbar dynamically created.
UIUtil.hideDisabledButtons(defaultToolbarButtons);
// Initialise the main toolbar. The main toolbar will only take into
// account it's own configuration from interface_config.
this._initMainToolbarButtons();
Object.keys(defaultToolbarButtons).forEach(
id => {
if (UIUtil.isButtonEnabled(id)) {
var button = defaultToolbarButtons[id];
let button = defaultToolbarButtons[id];
let buttonElement = document.getElementById(button.id);
let tooltipPosition
= (interfaceConfig.MAIN_TOOLBAR_BUTTONS
.indexOf(id) > -1)
? "bottom" : "right";
UIUtil.setTooltip( buttonElement,
button.tooltipKey,
tooltipPosition);
if (button.shortcut)
APP.keyboardshortcut.registerShortcut(
@@ -355,8 +400,15 @@ const Toolbar = {
isVisible);
});
APP.UI.addListener(UIEvents.LOCAL_RAISE_HAND_CHANGED,
function(isRaisedHand) {
Toolbar._toggleRaiseHand(isRaisedHand);
});
if(!APP.tokenData.isGuest) {
$("#toolbar_button_profile").addClass("unclickable");
UIUtil.removeTooltip(
document.getElementById('toolbar_button_profile'));
}
},
/**
@@ -431,9 +483,11 @@ const Toolbar = {
// Shows or hides the 'shared video' button.
showSharedVideoButton () {
let $element = $('#toolbar_button_sharedvideo');
if (UIUtil.isButtonEnabled('sharedvideo')
&& config.disableThirdPartyRequests !== true) {
$('#toolbar_button_sharedvideo').css({display: "inline-block"});
$element.css({display: "inline-block"});
UIUtil.setTooltip($element.get(0), 'toolbar.sharedvideo', 'right');
} else {
$('#toolbar_button_sharedvideo').css({display: "none"});
}
@@ -518,6 +572,13 @@ const Toolbar = {
}
},
/**
* Toggles / untoggles the view for raised hand.
*/
_toggleRaiseHand(isRaisedHand) {
$('#toolbar_button_raisehand').toggleClass("toggled", isRaisedHand);
},
/**
* Marks video icon as muted or not.
* @param {boolean} muted if icon should look like muted or not
@@ -679,16 +740,9 @@ const Toolbar = {
}
);
},
/**
* TODO: Fix mic popups
* <a class="button icon-microphone" id="toolbar_button_mute" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="mutePopover" data-i18n="[content]toolbar.mute" content="Mute / Unmute">
* <ul id="micMutedPopup" class="loginmenu">
* <li data-i18n="[html]toolbar.micMutedPopup"></li>
* </ul>
* <ul id="unableToUnmutePopup" class="loginmenu">
* <li data-i18n="[html]toolbar.unableToUnmutePopup"></li>
* </ul>
* </a>
* Initialise main toolbar buttons.
*/
_initMainToolbarButtons() {
interfaceConfig.MAIN_TOOLBAR_BUTTONS.forEach((value, index) => {
@@ -730,7 +784,6 @@ const Toolbar = {
buttonElement.setAttribute("data-i18n", button.i18n);
buttonElement.setAttribute("data-container", "body");
buttonElement.setAttribute("data-toggle", "popover");
buttonElement.setAttribute("data-placement", "bottom");
this._addPopups(buttonElement, button.popups);
@@ -751,4 +804,4 @@ const Toolbar = {
}
};
export default Toolbar;
export default Toolbar;

View File

@@ -35,9 +35,11 @@ function hideToolbar(force) {
clearTimeout(toolbarTimeoutObject);
toolbarTimeoutObject = null;
if (Toolbar.isHovered() || APP.UI.isRingOverlayVisible()) {
if (Toolbar.isHovered()
|| APP.UI.isRingOverlayVisible()
|| SideContainerToggler.isVisible()) {
toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
} else if (!SideContainerToggler.isVisible() || force) {
} else {
Toolbar.hide();
$('#subject').animate({top: "-=40"}, 300);
}
@@ -50,7 +52,8 @@ const ToolbarToggler = {
init() {
alwaysVisibleToolbar = (config.alwaysVisibleToolbar === true);
this._registerWindowClickListeners();
// disabled
//this._registerWindowClickListeners();
},
/**

View File

@@ -1,4 +1,6 @@
/* global $, config, interfaceConfig */
/* global $, APP, config, AJS, interfaceConfig */
import KeyboardShortcut from '../../keyboardshortcut/keyboardshortcut';
/**
* Created by hristo on 12/22/14.
@@ -82,12 +84,59 @@
context.putImageData(imgData, 0, 0);
},
/**
* Sets the tooltip to the given element.
*
* @param element the element to set the tooltip to
* @param key the tooltip data-i18n key
* @param position the position of the tooltip in relation to the element
*/
setTooltip: function (element, key, position) {
element.setAttribute("data-i18n", "[data-content]" + key);
element.setAttribute("data-toggle", "popover");
element.setAttribute("data-placement", position);
element.setAttribute("data-html", true);
element.setAttribute("data-container", "body");
let positions = {
'top': 's',
'top-left': 'se',
'left': 'e',
'bottom-left': 'ne',
'bottom': 'n',
'bottom-right': 'nw',
'right': 'w',
'top-right': 'sw'
};
element.setAttribute("data-i18n", "[content]" + key);
APP.translation.translateElement($(element));
AJS.$(element).tooltip({
gravity: positions[position],
title: this._getTooltipText.bind(this, element),
html: true
});
},
/**
* Removes the tooltip to the given element.
*
* @param element the element to remove the tooltip from
*/
removeTooltip: function (element) {
AJS.$(element).tooltip('destroy');
},
/**
* Internal util function for generating tooltip title.
*
* @param element
* @returns {string|*}
* @private
*/
_getTooltipText: function (element) {
let title = element.getAttribute('content');
let shortcut = element.getAttribute('shortcut');
if(shortcut) {
let shortcutString = KeyboardShortcut.getShortcutTooltip(shortcut);
title += ` ${shortcutString}`;
}
return title;
},
/**
@@ -112,7 +161,8 @@
* is enabled, {false} - otherwise
*/
isButtonEnabled: function (name) {
return interfaceConfig.TOOLBAR_BUTTONS.indexOf(name) !== -1;
return interfaceConfig.TOOLBAR_BUTTONS.indexOf(name) !== -1
|| interfaceConfig.MAIN_TOOLBAR_BUTTONS.indexOf(name) !== -1;
},
/**
* Indicates if the setting section is enabled.
@@ -232,6 +282,23 @@
*/
parseCssInt(cssValue) {
return parseInt(cssValue) || 0;
},
/**
* Adds href value to 'a' link jquery object. If link value is null,
* undefined or empty string, disables the link.
* @param {object} aLinkElement the jquery object
* @param {string} link the link value
*/
setLinkHref(aLinkElement, link) {
if (link) {
aLinkElement.attr('href', link);
} else {
aLinkElement.css({
"pointer-events": "none",
"cursor": "default"
});
}
}
};

View File

@@ -312,7 +312,6 @@ ConnectionIndicator.prototype.updateConnectionQuality =
} else {
if(this.connectionIndicatorContainer.style.display == "none") {
this.connectionIndicatorContainer.style.display = "block";
this.videoContainer.updateIconPositions();
}
}
this.bandwidth = object.bandwidth;

View File

@@ -3,8 +3,6 @@
import UIEvents from "../../../service/UI/UIEvents";
import UIUtil from "../util/UIUtil";
const thumbAspectRatio = 1 / 1;
const FilmStrip = {
/**
*
@@ -26,7 +24,7 @@ const FilmStrip = {
*/
toggleFilmStrip (visible) {
if (typeof visible === 'boolean'
&& this.isFilmStripVisible() == visible) {
&& this.isFilmStripVisible() == visible) {
return;
}
@@ -36,8 +34,8 @@ const FilmStrip = {
var eventEmitter = this.eventEmitter;
if (eventEmitter) {
eventEmitter.emit(
UIEvents.TOGGLED_FILM_STRIP,
this.isFilmStripVisible());
UIEvents.TOGGLED_FILM_STRIP,
this.isFilmStripVisible());
}
},
@@ -66,13 +64,52 @@ const FilmStrip = {
- parseInt(this.filmStrip.css('paddingRight'), 10);
},
/**
* Calculates the thumbnail size.
*/
calculateThumbnailSize () {
let availableHeight = interfaceConfig.FILM_STRIP_MAX_HEIGHT;
calculateThumbnailSize() {
let availableSizes = this.calculateAvailableSize();
let width = availableSizes.availableWidth;
let height = availableSizes.availableHeight;
let numvids = this.getThumbs(true).length;
return this.calculateThumbnailSizeFromAvailable(width, height);
},
/**
* Normalizes local and remote thumbnail ratios
*/
normalizeThumbnailRatio () {
let remoteHeightRatio = interfaceConfig.REMOTE_THUMBNAIL_RATIO_HEIGHT;
let remoteWidthRatio = interfaceConfig.REMOTE_THUMBNAIL_RATIO_WIDTH;
let localHeightRatio = interfaceConfig.LOCAL_THUMBNAIL_RATIO_HEIGHT;
let localWidthRatio = interfaceConfig.LOCAL_THUMBNAIL_RATIO_WIDTH;
let commonHeightRatio = remoteHeightRatio * localHeightRatio;
let localRatioCoefficient = localWidthRatio / localHeightRatio;
let remoteRatioCoefficient = remoteWidthRatio / remoteHeightRatio;
remoteWidthRatio = commonHeightRatio * remoteRatioCoefficient;
remoteHeightRatio = commonHeightRatio;
localWidthRatio = commonHeightRatio * localRatioCoefficient;
localHeightRatio = commonHeightRatio;
let localRatio = {
widthRatio: localWidthRatio,
heightRatio: localHeightRatio
};
let remoteRatio = {
widthRatio: remoteWidthRatio,
heightRatio: remoteHeightRatio
};
return { localRatio, remoteRatio };
},
calculateAvailableSize() {
let availableHeight = interfaceConfig.FILM_STRIP_MAX_HEIGHT;
let thumbs = this.getThumbs(true);
let numvids = thumbs.remoteThumbs.length;
let localVideoContainer = $("#localVideoContainer");
@@ -83,20 +120,19 @@ const FilmStrip = {
*/
let videoAreaAvailableWidth
= UIUtil.getAvailableVideoWidth()
- UIUtil.parseCssInt(this.filmStrip.css('right'), 10)
- UIUtil.parseCssInt(this.filmStrip.css('paddingLeft'), 10)
- UIUtil.parseCssInt(this.filmStrip.css('paddingRight'), 10)
- UIUtil.parseCssInt(this.filmStrip.css('borderLeftWidth'), 10)
- UIUtil.parseCssInt(this.filmStrip.css('borderRightWidth'), 10)
- UIUtil.parseCssInt(this.filmStrip.css('right'), 10)
- UIUtil.parseCssInt(this.filmStrip.css('paddingLeft'), 10)
- UIUtil.parseCssInt(this.filmStrip.css('paddingRight'), 10)
- UIUtil.parseCssInt(this.filmStrip.css('borderLeftWidth'), 10)
- UIUtil.parseCssInt(this.filmStrip.css('borderRightWidth'), 10)
- 5;
let availableWidth = videoAreaAvailableWidth;
// If the number of videos is 0 or undefined we don't need to calculate
// further.
if (numvids)
// If local thumb is not hidden
if(thumbs.localThumb) {
availableWidth = Math.floor(
(videoAreaAvailableWidth - numvids * (
(videoAreaAvailableWidth - (
UIUtil.parseCssInt(
localVideoContainer.css('borderLeftWidth'), 10)
+ UIUtil.parseCssInt(
@@ -109,36 +145,90 @@ const FilmStrip = {
localVideoContainer.css('marginLeft'), 10)
+ UIUtil.parseCssInt(
localVideoContainer.css('marginRight'), 10)))
/ numvids);
);
}
// If the number of videos is 0 or undefined we don't need to calculate
// further.
if (numvids) {
let remoteVideoContainer = thumbs.remoteThumbs.eq(0);
availableWidth = Math.floor(
(videoAreaAvailableWidth - numvids * (
UIUtil.parseCssInt(
remoteVideoContainer.css('borderLeftWidth'), 10)
+ UIUtil.parseCssInt(
remoteVideoContainer.css('borderRightWidth'), 10)
+ UIUtil.parseCssInt(
remoteVideoContainer.css('paddingLeft'), 10)
+ UIUtil.parseCssInt(
remoteVideoContainer.css('paddingRight'), 10)
+ UIUtil.parseCssInt(
remoteVideoContainer.css('marginLeft'), 10)
+ UIUtil.parseCssInt(
remoteVideoContainer.css('marginRight'), 10)))
);
}
let maxHeight
// If the MAX_HEIGHT property hasn't been specified
// we have the static value.
= Math.min( interfaceConfig.FILM_STRIP_MAX_HEIGHT || 120,
availableHeight);
= Math.min(interfaceConfig.FILM_STRIP_MAX_HEIGHT || 120,
availableHeight);
availableHeight
= Math.min( maxHeight, window.innerHeight - 18);
= Math.min(maxHeight, window.innerHeight - 18);
if (availableHeight < availableWidth) {
availableWidth = availableHeight;
return { availableWidth, availableHeight };
},
calculateThumbnailSizeFromAvailable(availableWidth, availableHeight) {
let { localRatio, remoteRatio } = this.normalizeThumbnailRatio();
let { remoteThumbs } = this.getThumbs(true);
let remoteProportion = remoteRatio.widthRatio * remoteThumbs.length;
let widthProportion = remoteProportion + localRatio.widthRatio;
let heightUnit = availableHeight / localRatio.heightRatio;
let widthUnit = availableWidth / widthProportion;
if (heightUnit < widthUnit) {
widthUnit = heightUnit;
}
else
availableHeight = availableWidth;
heightUnit = widthUnit;
let localVideo = {
thumbWidth: widthUnit * localRatio.widthRatio,
thumbHeight: heightUnit * localRatio.heightRatio
};
let remoteVideo = {
thumbWidth: widthUnit * remoteRatio.widthRatio,
thumbHeight: widthUnit * remoteRatio.heightRatio
};
return {
thumbWidth: availableWidth,
thumbHeight: availableHeight
localVideo,
remoteVideo
};
},
resizeThumbnails (thumbWidth, thumbHeight,
resizeThumbnails (local, remote,
animate = false, forceUpdate = false) {
return new Promise(resolve => {
this.getThumbs(!forceUpdate).animate({
height: thumbHeight,
width: thumbWidth
let thumbs = this.getThumbs(!forceUpdate);
thumbs.localThumb.animate({
height: local.thumbHeight,
width: local.thumbWidth
}, {
queue: false,
duration: animate ? 500 : 0,
complete: resolve
});
thumbs.remoteThumbs.animate({
height: remote.thumbHeight,
width: remote.thumbWidth
}, {
queue: false,
duration: animate ? 500 : 0,
@@ -147,7 +237,7 @@ const FilmStrip = {
this.filmStrip.animate({
// adds 2 px because of small video 1px border
height: thumbHeight + 2
height: remote.thumbHeight + 2
}, {
queue: false,
duration: animate ? 500 : 0
@@ -165,13 +255,19 @@ const FilmStrip = {
selector += ':visible';
}
let localThumb = $("#localVideoContainer");
let remoteThumbs = this.filmStrip.children(selector)
.not("#localVideoContainer");
// Exclude the local video container if it has been hidden.
if ($("#localVideoContainer").hasClass("hidden"))
return this.filmStrip.children(selector)
.not("#localVideoContainer");
else
return this.filmStrip.children(selector);
}
if (localThumb.hasClass("hidden")) {
return { remoteThumbs };
} else {
return { remoteThumbs, localThumb };
}
},
};
export default FilmStrip;
export default FilmStrip;

View File

@@ -0,0 +1,310 @@
/* global $, APP, interfaceConfig */
/* jshint -W101 */
import Avatar from "../avatar/Avatar";
import {createDeferred} from '../../util/helpers';
import UIUtil from "../util/UIUtil";
import {VideoContainer, VIDEO_CONTAINER_TYPE} from "./VideoContainer";
/**
* Manager for all Large containers.
*/
export default class LargeVideoManager {
constructor (emitter) {
this.containers = {};
this.state = VIDEO_CONTAINER_TYPE;
this.videoContainer = new VideoContainer(
() => this.resizeContainer(VIDEO_CONTAINER_TYPE), emitter);
this.addContainer(VIDEO_CONTAINER_TYPE, this.videoContainer);
// use the same video container to handle and desktop tracks
this.addContainer("desktop", this.videoContainer);
this.width = 0;
this.height = 0;
this.$container = $('#largeVideoContainer');
this.$container.css({
display: 'inline-block'
});
if (interfaceConfig.SHOW_JITSI_WATERMARK) {
let leftWatermarkDiv
= this.$container.find("div.watermark.leftwatermark");
leftWatermarkDiv.css({display: 'block'});
UIUtil.setLinkHref(
leftWatermarkDiv.parent(),
interfaceConfig.JITSI_WATERMARK_LINK);
}
if (interfaceConfig.SHOW_BRAND_WATERMARK) {
let rightWatermarkDiv
= this.$container.find("div.watermark.rightwatermark");
rightWatermarkDiv.css({
display: 'block',
backgroundImage: 'url(images/rightwatermark.png)'
});
UIUtil.setLinkHref(
rightWatermarkDiv.parent(),
interfaceConfig.BRAND_WATERMARK_LINK);
}
if (interfaceConfig.SHOW_POWERED_BY) {
this.$container.children("a.poweredby").css({display: 'block'});
}
this.$container.hover(
e => this.onHoverIn(e),
e => this.onHoverOut(e)
);
}
onHoverIn (e) {
if (!this.state) {
return;
}
let container = this.getContainer(this.state);
container.onHoverIn(e);
}
onHoverOut (e) {
if (!this.state) {
return;
}
let container = this.getContainer(this.state);
container.onHoverOut(e);
}
get id () {
let container = this.getContainer(this.state);
return container.id;
}
scheduleLargeVideoUpdate () {
if (this.updateInProcess || !this.newStreamData) {
return;
}
this.updateInProcess = true;
let container = this.getContainer(this.state);
// Include hide()/fadeOut only if we're switching between users
let preUpdate;
if (this.newStreamData.id != this.id) {
preUpdate = container.hide();
} else {
preUpdate = Promise.resolve();
}
preUpdate.then(() => {
let {id, stream, videoType, resolve} = this.newStreamData;
this.newStreamData = null;
console.info("hover in %s", id);
this.state = videoType;
let container = this.getContainer(this.state);
container.setStream(stream, videoType);
// change the avatar url on large
this.updateAvatar(Avatar.getAvatarUrl(id));
// If we the continer is VIDEO_CONTAINER_TYPE, we need to check
// its stream whether exist and is muted to set isVideoMuted
// in rest of the cases it is false
let isVideoMuted = false;
if (videoType == VIDEO_CONTAINER_TYPE)
isVideoMuted = stream ? stream.isMuted() : true;
// show the avatar on large if needed
container.showAvatar(isVideoMuted);
let promise;
// do not show stream if video is muted
// but we still should show watermark
if (isVideoMuted) {
this.showWatermark(true);
promise = Promise.resolve();
} else {
promise = container.show();
}
// resolve updateLargeVideo promise after everything is done
promise.then(resolve);
return promise;
}).then(() => {
// after everything is done check again if there are any pending
// new streams.
this.updateInProcess = false;
this.scheduleLargeVideoUpdate();
});
}
/**
* Update large video.
* Switches to large video even if previously other container was visible.
* @param userID the userID of the participant associated with the stream
* @param {JitsiTrack?} stream new stream
* @param {string?} videoType new video type
* @returns {Promise}
*/
updateLargeVideo (userID, stream, videoType) {
if (this.newStreamData) {
this.newStreamData.reject();
}
this.newStreamData = createDeferred();
this.newStreamData.id = userID;
this.newStreamData.stream = stream;
this.newStreamData.videoType = videoType;
this.scheduleLargeVideoUpdate();
return this.newStreamData.promise;
}
/**
* Update container size.
*/
updateContainerSize () {
this.width = UIUtil.getAvailableVideoWidth();
this.height = window.innerHeight;
}
/**
* Resize Large container of specified type.
* @param {string} type type of container which should be resized.
* @param {boolean} [animate=false] if resize process should be animated.
*/
resizeContainer (type, animate = false) {
let container = this.getContainer(type);
container.resize(this.width, this.height, animate);
}
/**
* Resize all Large containers.
* @param {boolean} animate if resize process should be animated.
*/
resize (animate) {
// resize all containers
Object.keys(this.containers)
.forEach(type => this.resizeContainer(type, animate));
this.$container.animate({
width: this.width,
height: this.height
}, {
queue: false,
duration: animate ? 500 : 0
});
}
/**
* Enables/disables the filter indicating a video problem to the user.
*
* @param enable <tt>true</tt> to enable, <tt>false</tt> to disable
*/
enableVideoProblemFilter (enable) {
let container = this.getContainer(this.state);
container.$video.toggleClass("videoProblemFilter", enable);
}
/**
* Updates the src of the dominant speaker avatar
*/
updateAvatar (avatarUrl) {
$("#dominantSpeakerAvatar").attr('src', avatarUrl);
}
/**
* Show or hide watermark.
* @param {boolean} show
*/
showWatermark (show) {
$('.watermark').css('visibility', show ? 'visible' : 'hidden');
}
/**
* Add container of specified type.
* @param {string} type container type
* @param {LargeContainer} container container to add.
*/
addContainer (type, container) {
if (this.containers[type]) {
throw new Error(`container of type ${type} already exist`);
}
this.containers[type] = container;
this.resizeContainer(type);
}
/**
* Get Large container of specified type.
* @param {string} type container type.
* @returns {LargeContainer}
*/
getContainer (type) {
let container = this.containers[type];
if (!container) {
throw new Error(`container of type ${type} doesn't exist`);
}
return container;
}
/**
* Remove Large container of specified type.
* @param {string} type container type.
*/
removeContainer (type) {
if (!this.containers[type]) {
throw new Error(`container of type ${type} doesn't exist`);
}
delete this.containers[type];
}
/**
* Show Large container of specified type.
* Does nothing if such container is already visible.
* @param {string} type container type.
* @returns {Promise}
*/
showContainer (type) {
if (this.state === type) {
return Promise.resolve();
}
let oldContainer = this.containers[this.state];
if (this.state === VIDEO_CONTAINER_TYPE) {
this.showWatermark(false);
}
oldContainer.hide();
this.state = type;
let container = this.getContainer(type);
return container.show().then(() => {
if (type === VIDEO_CONTAINER_TYPE) {
this.showWatermark(true);
}
});
}
/**
* Changes the flipX state of the local video.
* @param val {boolean} true if flipped.
*/
onLocalFlipXChange(val) {
this.videoContainer.setLocalFlipX(val);
}
}

View File

@@ -11,7 +11,6 @@ function LocalVideo(VideoLayout, emitter) {
this.videoSpanId = "localVideoContainer";
this.container = $("#localVideoContainer").get(0);
this.localVideoId = null;
this.bindHoverHandler();
if(config.enableLocalVideoFlip)
this._buildContextMenu();
this.isLocal = true;
@@ -44,7 +43,7 @@ function createEditDisplayNameButton() {
editButton.className = 'displayname';
UIUtil.setTooltip(editButton,
"videothumbnail.editnickname",
"top");
"left");
editButton.innerHTML = '<i class="icon-edit"></i>';
return editButton;
@@ -61,7 +60,7 @@ LocalVideo.prototype.setDisplayName = function(displayName, key) {
return;
}
var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
var nameSpan = $('#' + this.videoSpanId + ' .displayname');
var defaultLocalDisplayName = APP.translation.generateTranslationHTML(
interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
@@ -72,7 +71,7 @@ LocalVideo.prototype.setDisplayName = function(displayName, key) {
if (displayName && displayName.length > 0) {
meHTML = APP.translation.generateTranslationHTML("me");
$('#localDisplayName').html(
UIUtil.escapeHtml(displayName) + ' (' + meHTML + ')'
`${UIUtil.escapeHtml(displayName)} (${meHTML})`
);
} else {
$('#localDisplayName').html(defaultLocalDisplayName);
@@ -80,11 +79,11 @@ LocalVideo.prototype.setDisplayName = function(displayName, key) {
}
this.updateView();
} else {
var editButton = createEditDisplayNameButton();
nameSpan = document.createElement('span');
nameSpan.className = 'displayname';
$('#' + this.videoSpanId)[0].appendChild(nameSpan);
document.getElementById(this.videoSpanId)
.querySelector('.videocontainer__toolbar')
.appendChild(nameSpan);
if (displayName && displayName.length > 0) {
@@ -97,12 +96,11 @@ LocalVideo.prototype.setDisplayName = function(displayName, key) {
nameSpan.id = 'localDisplayName';
this.container.appendChild(editButton);
//translates popover of edit button
APP.translation.translateElement($("a.displayname"));
var editableText = document.createElement('input');
editableText.className = 'displayname';
editableText.className = 'editdisplayname';
editableText.type = 'text';
editableText.id = 'editDisplayName';
@@ -119,26 +117,30 @@ LocalVideo.prototype.setDisplayName = function(displayName, key) {
JSON.stringify({name: "Jane Pink"}));
editableText.setAttribute("placeholder", defaultNickname);
this.container.appendChild(editableText);
this.container
.querySelector('.videocontainer__toolbar')
.appendChild(editableText);
var self = this;
$('#localVideoContainer .displayname')
.bind("click", function (e) {
let $editDisplayName = $('#editDisplayName');
let $localDisplayName = $('#localDisplayName');
var editDisplayName = $('#editDisplayName');
e.preventDefault();
e.stopPropagation();
$('#localDisplayName').hide();
editDisplayName.show();
editDisplayName.focus();
editDisplayName.select();
$localDisplayName.hide();
$editDisplayName.show();
$editDisplayName.focus();
$editDisplayName.select();
editDisplayName.one("focusout", function (e) {
$editDisplayName.one("focusout", function (e) {
self.emitter.emit(UIEvents.NICKNAME_CHANGED, this.value);
$('#editDisplayName').hide();
$editDisplayName.hide();
$localDisplayName.show();
});
editDisplayName.on('keydown', function (e) {
$editDisplayName.on('keydown', function (e) {
if (e.keyCode === 13) {
e.preventDefault();
$('#editDisplayName').hide();

View File

@@ -17,10 +17,8 @@ function RemoteVideo(id, VideoLayout, emitter) {
this.addRemoteVideoContainer();
this.connectionIndicator = new ConnectionIndicator(this, id);
this.setDisplayName();
this.bindHoverHandler();
this.flipX = false;
this.isLocal = false;
this.isMuted = false;
}
RemoteVideo.prototype = Object.create(SmallVideo.prototype);
@@ -34,8 +32,10 @@ RemoteVideo.prototype.addRemoteVideoContainer = function() {
if (APP.conference.isModerator) {
this.addRemoteVideoMenu();
}
let {thumbWidth, thumbHeight} = this.VideoLayout.resizeThumbnails();
AudioLevels.updateAudioLevelCanvas(this.id, thumbWidth, thumbHeight);
let { remoteVideo } = this.VideoLayout.resizeThumbnails();
let { thumbHeight, thumbWidth } = remoteVideo;
AudioLevels.createAudioLevelCanvas(this.id, thumbWidth, thumbHeight);
return this.container;
};
@@ -50,7 +50,7 @@ RemoteVideo.prototype.addRemoteVideoContainer = function() {
*/
RemoteVideo.prototype._initPopupMenu = function (popupMenuElement) {
this.popover = new JitsiPopover(
$("#" + this.videoSpanId + " > .remotevideomenu"),
$("#" + this.videoSpanId + " .remotevideomenu"),
{ content: popupMenuElement.outerHTML,
skin: "black"});
@@ -60,7 +60,7 @@ RemoteVideo.prototype._initPopupMenu = function (popupMenuElement) {
this.popover.show = function () {
// update content by forcing it, to finish even if popover
// is not visible
this.updateRemoteVideoMenu(this.isMuted, true);
this.updateRemoteVideoMenu(this.isAudioMuted, true);
// call the original show, passing its actual this
origShowFunc.call(this.popover);
}.bind(this);
@@ -96,7 +96,7 @@ RemoteVideo.prototype._generatePopupContent = function () {
muteLinkItem.id = "mutelink_" + this.id;
if (this.isMuted) {
if (this.isAudioMuted) {
muteLinkItem.innerHTML = mutedHTML;
muteLinkItem.className = 'mutelink disabled';
}
@@ -108,7 +108,7 @@ RemoteVideo.prototype._generatePopupContent = function () {
// Delegate event to the document.
$(document).on("click", "#mutelink_" + this.id, function(){
if (this.isMuted)
if (this.isAudioMuted)
return;
this.emitter.emit(UIEvents.REMOTE_AUDIO_MUTED, this.id);
@@ -152,7 +152,7 @@ RemoteVideo.prototype._generatePopupContent = function () {
*/
RemoteVideo.prototype.updateRemoteVideoMenu = function (isMuted, force) {
this.isMuted = isMuted;
this.isAudioMuted = isMuted;
// generate content, translate it and add it to document only if
// popover is visible or we force to do so.
@@ -170,12 +170,16 @@ RemoteVideo.prototype.updateRemoteVideoMenu = function (isMuted, force) {
*/
if (!interfaceConfig.filmStripOnly) {
RemoteVideo.prototype.addRemoteVideoMenu = function () {
var spanElement = document.createElement('div');
spanElement.className = 'remotevideomenu';
this.container.appendChild(spanElement);
var spanElement = document.createElement('span');
spanElement.className = 'remotevideomenu toolbar-icon right';
this.container
.querySelector('.videocontainer__toolbar')
.appendChild(spanElement);
var menuElement = document.createElement('i');
menuElement.className = 'fa fa-angle-down';
menuElement.className = 'icon-menu-up';
menuElement.title = 'Remote user controls';
spanElement.appendChild(menuElement);
@@ -381,7 +385,7 @@ RemoteVideo.prototype.setDisplayName = function(displayName, key) {
return;
}
var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
var nameSpan = $('#' + this.videoSpanId + ' .displayname');
// If we already have a display name for this video.
if (nameSpan.length > 0) {
@@ -400,7 +404,9 @@ RemoteVideo.prototype.setDisplayName = function(displayName, key) {
} else {
nameSpan = document.createElement('span');
nameSpan.className = 'displayname';
$('#' + this.videoSpanId)[0].appendChild(nameSpan);
$('#' + this.videoSpanId)[0]
.querySelector('.videocontainer__toolbar')
.appendChild(nameSpan);
if (displayName && displayName.length > 0) {
$(nameSpan).text(displayName);
@@ -418,7 +424,7 @@ RemoteVideo.prototype.setDisplayName = function(displayName, key) {
* @param videoElementId the id of local or remote video element.
*/
RemoteVideo.prototype.removeRemoteVideoMenu = function() {
var menuSpan = $('#' + this.videoSpanId + '>span.remotevideomenu');
var menuSpan = $('#' + this.videoSpanId + '> .remotevideomenu');
if (menuSpan.length) {
this.popover.forceHide();
menuSpan.remove();
@@ -427,12 +433,16 @@ RemoteVideo.prototype.removeRemoteVideoMenu = function() {
};
RemoteVideo.createContainer = function (spanId) {
var container = document.createElement('span');
let container = document.createElement('span');
container.id = spanId;
container.className = 'videocontainer';
let toolbar = document.createElement('div');
toolbar.className = "videocontainer__toolbar";
container.appendChild(toolbar);
var remotes = document.getElementById('remoteVideos');
return remotes.appendChild(container);
};
export default RemoteVideo;

View File

@@ -7,7 +7,7 @@ import UIEvents from "../../../service/UI/UIEvents";
const RTCUIHelper = JitsiMeetJS.util.RTCUIHelper;
function SmallVideo(VideoLayout) {
this.isMuted = false;
this.isAudioMuted = false;
this.hasAvatar = false;
this.isVideoMuted = false;
this.videoStream = null;
@@ -40,7 +40,7 @@ SmallVideo.prototype.isVisible = function () {
};
SmallVideo.prototype.showDisplayName = function(isShow) {
var nameSpan = $('#' + this.videoSpanId + '>span.displayname').get(0);
var nameSpan = $('#' + this.videoSpanId + ' .displayname').get(0);
if (isShow) {
if (nameSpan && nameSpan.innerHTML && nameSpan.innerHTML.length)
nameSpan.setAttribute("style", "display:inline-block;");
@@ -171,26 +171,6 @@ SmallVideo.getStreamElementID = function (stream) {
return (isVideo ? 'remoteVideo_' : 'remoteAudio_') + stream.getId();
};
/**
* Configures hoverIn/hoverOut handlers.
*/
SmallVideo.prototype.bindHoverHandler = function () {
// Add hover handler
var self = this;
$(this.container).hover(
function () {
self.showDisplayName(true);
},
function () {
// If the video has been "pinned" by the user we want to
// keep the display name on place.
if (!self.VideoLayout.isLargeVideoVisible() ||
!self.VideoLayout.isCurrentlyOnLarge(self.id))
self.showDisplayName(false);
}
);
};
/**
* Updates the data for the indicator
* @param id the id of the indicator
@@ -209,86 +189,102 @@ SmallVideo.prototype.hideIndicator = function () {
/**
* Shows audio muted indicator over small videos.
* @param {string} isMuted
* Shows / hides the audio muted indicator over small videos.
*
* @param {boolean} isMuted indicates if the muted element should be shown
* or hidden
*/
SmallVideo.prototype.showAudioIndicator = function(isMuted) {
var audioMutedSpan = $('#' + this.videoSpanId + '>span.audioMuted');
var audioMutedIndicator = this.getAudioMutedIndicator();
if (!isMuted) {
if (audioMutedSpan.length > 0) {
audioMutedSpan.popover('hide');
audioMutedSpan.remove();
}
audioMutedIndicator.hide();
}
else {
if (!audioMutedSpan.length) {
audioMutedSpan = document.createElement('span');
audioMutedSpan.className = 'audioMuted';
UIUtil.setTooltip(audioMutedSpan,
"videothumbnail.mute",
"top");
this.container.appendChild(audioMutedSpan);
APP.translation.translateElement($('#' + this.videoSpanId + " > span"));
var mutedIndicator = document.createElement('i');
mutedIndicator.className = 'icon-mic-disabled';
audioMutedSpan.appendChild(mutedIndicator);
}
this.updateIconPositions();
audioMutedIndicator.show();
}
this.isMuted = isMuted;
this.isAudioMuted = isMuted;
};
/**
* Returns the audio muted indicator jquery object. If it doesn't exists -
* creates it.
*
* @returns {jQuery|HTMLElement} the audio muted indicator
*/
SmallVideo.prototype.getAudioMutedIndicator = function () {
var audioMutedSpan = $('#' + this.videoSpanId + ' .audioMuted');
if (audioMutedSpan.length) {
return audioMutedSpan;
}
audioMutedSpan = document.createElement('span');
audioMutedSpan.className = 'audioMuted toolbar-icon';
UIUtil.setTooltip(audioMutedSpan,
"videothumbnail.mute",
"top");
this.container
.querySelector('.videocontainer__toolbar')
.appendChild(audioMutedSpan);
var mutedIndicator = document.createElement('i');
mutedIndicator.className = 'icon-mic-disabled';
audioMutedSpan.appendChild(mutedIndicator);
return $('#' + this.videoSpanId + ' .audioMuted');
};
/**
* Shows video muted indicator over small videos and disables/enables avatar
* if video muted.
*
* @param {boolean} isMuted indicates if we should set the view to muted view
* or not
*/
SmallVideo.prototype.setMutedView = function(isMuted) {
this.isVideoMuted = isMuted;
this.updateView();
var videoMutedSpan = $('#' + this.videoSpanId + '>span.videoMuted');
var videoMutedSpan = this.getVideoMutedIndicator();
if (isMuted === false) {
if (videoMutedSpan.length > 0) {
videoMutedSpan.remove();
}
}
else {
if (!videoMutedSpan.length) {
videoMutedSpan = document.createElement('span');
videoMutedSpan.className = 'videoMuted';
this.container.appendChild(videoMutedSpan);
var mutedIndicator = document.createElement('i');
mutedIndicator.className = 'icon-camera-disabled';
UIUtil.setTooltip(mutedIndicator,
"videothumbnail.videomute",
"top");
videoMutedSpan.appendChild(mutedIndicator);
//translate texts for muted indicator
APP.translation.translateElement($('#' + this.videoSpanId + " > span > i"));
}
this.updateIconPositions();
}
videoMutedSpan[isMuted ? 'show' : 'hide']();
};
SmallVideo.prototype.updateIconPositions = function () {
var audioMutedSpan = $('#' + this.videoSpanId + '>span.audioMuted');
var connectionIndicator = $('#' + this.videoSpanId + '>div.connectionindicator');
var videoMutedSpan = $('#' + this.videoSpanId + '>span.videoMuted');
if(connectionIndicator.length > 0 &&
connectionIndicator[0].style.display != "none") {
audioMutedSpan.css({right: "23px"});
videoMutedSpan.css({right: ((audioMutedSpan.length > 0? 23 : 0) + 30) + "px"});
} else {
audioMutedSpan.css({right: "0px"});
videoMutedSpan.css({right: (audioMutedSpan.length > 0? 30 : 0) + "px"});
/**
* Returns the video muted indicator jquery object. If it doesn't exists -
* creates it.
*
* @returns {jQuery|HTMLElement} the video muted indicator
*/
SmallVideo.prototype.getVideoMutedIndicator = function () {
var videoMutedSpan = $('#' + this.videoSpanId + ' .videoMuted');
if (videoMutedSpan.length) {
return videoMutedSpan;
}
videoMutedSpan = document.createElement('span');
videoMutedSpan.className = 'videoMuted toolbar-icon';
this.container
.querySelector('.videocontainer__toolbar')
.appendChild(videoMutedSpan);
var mutedIndicator = document.createElement('i');
mutedIndicator.className = 'icon-camera-disabled';
UIUtil.setTooltip(mutedIndicator,
"videothumbnail.videomute",
"top");
videoMutedSpan.appendChild(mutedIndicator);
return $('#' + this.videoSpanId + ' .videoMuted');
};
/**
@@ -298,26 +294,25 @@ SmallVideo.prototype.createModeratorIndicatorElement = function () {
// Show moderator indicator
var indicatorSpan = $('#' + this.videoSpanId + ' .focusindicator');
if (!indicatorSpan || indicatorSpan.length === 0) {
indicatorSpan = document.createElement('span');
indicatorSpan.className = 'focusindicator';
this.container.appendChild(indicatorSpan);
indicatorSpan = $('#' + this.videoSpanId + ' .focusindicator');
if (indicatorSpan.length) {
return;
}
if (indicatorSpan.children().length !== 0)
return;
indicatorSpan = document.createElement('span');
indicatorSpan.className = 'focusindicator toolbar-icon right';
this.container
.querySelector('.videocontainer__toolbar')
.appendChild(indicatorSpan);
var moderatorIndicator = document.createElement('i');
moderatorIndicator.className = 'icon-star';
indicatorSpan[0].appendChild(moderatorIndicator);
UIUtil.setTooltip(indicatorSpan[0],
UIUtil.setTooltip(moderatorIndicator,
"videothumbnail.moderator",
"top");
"top-left");
//translates text in focus indicators
APP.translation.translateElement($('#' + this.videoSpanId + ' .focusindicator'));
indicatorSpan.appendChild(moderatorIndicator);
};
/**
@@ -406,8 +401,6 @@ SmallVideo.prototype.updateView = function () {
setVisibility(video, showVideo);
}
setVisibility(avatar, showAvatar);
this.showDisplayName(!showVideo && !showAvatar);
};
SmallVideo.prototype.avatarChanged = function (avatarUrl) {
@@ -445,7 +438,7 @@ SmallVideo.prototype.showDominantSpeakerIndicator = function (show) {
indicatorSpan.innerHTML
= "<i id='indicatoricon' class='fa fa-bullhorn'></i>";
// adds a tooltip
UIUtil.setTooltip(indicatorSpan, "speaker", "left");
UIUtil.setTooltip(indicatorSpan, "speaker", "top");
APP.translation.translateElement($(indicatorSpan));
$(indicatorSpan).css("visibility", show ? "visible" : "hidden");
@@ -465,12 +458,11 @@ SmallVideo.prototype.showRaisedHandIndicator = function (show) {
var indicatorSpanId = "raisehandindicator";
var indicatorSpan = this.getIndicatorSpan(indicatorSpanId);
indicatorSpan.style.background = "#D6D61E";
indicatorSpan.innerHTML
= "<i id='indicatoricon' class='fa fa-hand-paper-o'></i>";
= "<i id='indicatoricon' class='icon-raised-hand'></i>";
// adds a tooltip
UIUtil.setTooltip(indicatorSpan, "raisedHand", "left");
UIUtil.setTooltip(indicatorSpan, "raisedHand", "top");
APP.translation.translateElement($(indicatorSpan));
$(indicatorSpan).css("visibility", show ? "visible" : "hidden");

View File

@@ -1,17 +1,16 @@
/* global $, APP, interfaceConfig */
/* jshint -W101 */
import UIUtil from "../util/UIUtil";
import UIEvents from "../../../service/UI/UIEvents";
import LargeContainer from './LargeContainer';
import FilmStrip from './FilmStrip';
import Avatar from "../avatar/Avatar";
import {createDeferred} from '../../util/helpers';
import LargeContainer from './LargeContainer';
import UIEvents from "../../../service/UI/UIEvents";
import UIUtil from "../util/UIUtil";
// FIXME should be 'video'
export const VIDEO_CONTAINER_TYPE = "camera";
const FADE_DURATION_MS = 300;
export const VIDEO_CONTAINER_TYPE = "camera";
/**
* Get stream id.
* @param {JitsiTrack?} stream
@@ -20,7 +19,8 @@ function getStreamOwnerId(stream) {
if (!stream) {
return;
}
if (stream.isLocal()) { // local stream doesn't have method "getParticipantId"
// local stream doesn't have method "getParticipantId"
if (stream.isLocal()) {
return APP.conference.getMyUserId();
} else {
return stream.getParticipantId();
@@ -154,7 +154,7 @@ function getDesktopVideoPosition(videoWidth,
/**
* Container for user video.
*/
class VideoContainer extends LargeContainer {
export class VideoContainer extends LargeContainer {
// FIXME: With Temasys we have to re-select everytime
get $video () {
return $('#largeVideo');
@@ -164,11 +164,12 @@ class VideoContainer extends LargeContainer {
return getStreamOwnerId(this.stream);
}
constructor (onPlay) {
constructor (onPlay, emitter) {
super();
this.stream = null;
this.videoType = null;
this.localFlipX = true;
this.emitter = emitter;
this.isVisible = false;
@@ -205,14 +206,14 @@ class VideoContainer extends LargeContainer {
let { width, height } = this.getStreamSize();
if (this.stream && this.isScreenSharing()) {
return getDesktopVideoSize( width,
height,
containerWidth,
containerHeight);
height,
containerWidth,
containerHeight);
} else {
return getCameraVideoSize( width,
height,
containerWidth,
containerHeight);
height,
containerWidth,
containerHeight);
}
}
@@ -228,14 +229,14 @@ class VideoContainer extends LargeContainer {
getVideoPosition (width, height, containerWidth, containerHeight) {
if (this.stream && this.isScreenSharing()) {
return getDesktopVideoPosition( width,
height,
containerWidth,
containerHeight);
height,
containerWidth,
containerHeight);
} else {
return getCameraVideoPosition( width,
height,
containerWidth,
containerHeight);
height,
containerWidth,
containerHeight);
}
}
@@ -244,7 +245,7 @@ class VideoContainer extends LargeContainer {
= this.getVideoSize(containerWidth, containerHeight);
let { horizontalIndent, verticalIndent }
= this.getVideoPosition(width, height,
containerWidth, containerHeight);
containerWidth, containerHeight);
// update avatar position
let top = containerHeight / 2 - this.avatarHeight / 4 * 3;
@@ -327,6 +328,8 @@ class VideoContainer extends LargeContainer {
(show) ? interfaceConfig.DEFAULT_BACKGROUND : "#000");
this.$avatar.css("visibility", show ? "visible" : "hidden");
this.emitter.emit(UIEvents.LARGE_VIDEO_AVATAR_DISPLAYED, show);
}
// We are doing fadeOut/fadeIn animations on parent div which wraps
@@ -380,304 +383,3 @@ class VideoContainer extends LargeContainer {
return false;
}
}
/**
* Manager for all Large containers.
*/
export default class LargeVideoManager {
constructor () {
this.containers = {};
this.state = VIDEO_CONTAINER_TYPE;
this.videoContainer = new VideoContainer(
() => this.resizeContainer(VIDEO_CONTAINER_TYPE));
this.addContainer(VIDEO_CONTAINER_TYPE, this.videoContainer);
// use the same video container to handle and desktop tracks
this.addContainer("desktop", this.videoContainer);
this.width = 0;
this.height = 0;
this.$container = $('#largeVideoContainer');
this.$container.css({
display: 'inline-block'
});
if (interfaceConfig.SHOW_JITSI_WATERMARK) {
let leftWatermarkDiv
= this.$container.find("div.watermark.leftwatermark");
leftWatermarkDiv.css({display: 'block'});
leftWatermarkDiv.parent().attr(
'href', interfaceConfig.JITSI_WATERMARK_LINK);
}
if (interfaceConfig.SHOW_BRAND_WATERMARK) {
let rightWatermarkDiv
= this.$container.find("div.watermark.rightwatermark");
rightWatermarkDiv.css({
display: 'block',
backgroundImage: 'url(images/rightwatermark.png)'
});
rightWatermarkDiv.parent().attr(
'href', interfaceConfig.BRAND_WATERMARK_LINK);
}
if (interfaceConfig.SHOW_POWERED_BY) {
this.$container.children("a.poweredby").css({display: 'block'});
}
this.$container.hover(
e => this.onHoverIn(e),
e => this.onHoverOut(e)
);
}
onHoverIn (e) {
if (!this.state) {
return;
}
let container = this.getContainer(this.state);
container.onHoverIn(e);
}
onHoverOut (e) {
if (!this.state) {
return;
}
let container = this.getContainer(this.state);
container.onHoverOut(e);
}
get id () {
let container = this.getContainer(this.state);
return container.id;
}
scheduleLargeVideoUpdate () {
if (this.updateInProcess || !this.newStreamData) {
return;
}
this.updateInProcess = true;
let container = this.getContainer(this.state);
// Include hide()/fadeOut only if we're switching between users
let preUpdate;
if (this.newStreamData.id != this.id) {
preUpdate = container.hide();
} else {
preUpdate = Promise.resolve();
}
preUpdate.then(() => {
let {id, stream, videoType, resolve} = this.newStreamData;
this.newStreamData = null;
console.info("hover in %s", id);
this.state = videoType;
let container = this.getContainer(this.state);
container.setStream(stream, videoType);
// change the avatar url on large
this.updateAvatar(Avatar.getAvatarUrl(id));
// If we the continer is VIDEO_CONTAINER_TYPE, we need to check
// its stream whether exist and is muted to set isVideoMuted
// in rest of the cases it is false
let isVideoMuted = false;
if (videoType == VIDEO_CONTAINER_TYPE)
isVideoMuted = stream ? stream.isMuted() : true;
// show the avatar on large if needed
container.showAvatar(isVideoMuted);
let promise;
// do not show stream if video is muted
// but we still should show watermark
if (isVideoMuted) {
this.showWatermark(true);
promise = Promise.resolve();
} else {
promise = container.show();
}
// resolve updateLargeVideo promise after everything is done
promise.then(resolve);
return promise;
}).then(() => {
// after everything is done check again if there are any pending
// new streams.
this.updateInProcess = false;
this.scheduleLargeVideoUpdate();
});
}
/**
* Update large video.
* Switches to large video even if previously other container was visible.
* @param userID the userID of the participant associated with the stream
* @param {JitsiTrack?} stream new stream
* @param {string?} videoType new video type
* @returns {Promise}
*/
updateLargeVideo (userID, stream, videoType) {
if (this.newStreamData) {
this.newStreamData.reject();
}
this.newStreamData = createDeferred();
this.newStreamData.id = userID;
this.newStreamData.stream = stream;
this.newStreamData.videoType = videoType;
this.scheduleLargeVideoUpdate();
return this.newStreamData.promise;
}
/**
* Update container size.
*/
updateContainerSize () {
this.width = UIUtil.getAvailableVideoWidth();
this.height = window.innerHeight;
}
/**
* Resize Large container of specified type.
* @param {string} type type of container which should be resized.
* @param {boolean} [animate=false] if resize process should be animated.
*/
resizeContainer (type, animate = false) {
let container = this.getContainer(type);
container.resize(this.width, this.height, animate);
}
/**
* Resize all Large containers.
* @param {boolean} animate if resize process should be animated.
*/
resize (animate) {
// resize all containers
Object.keys(this.containers)
.forEach(type => this.resizeContainer(type, animate));
this.$container.animate({
width: this.width,
height: this.height
}, {
queue: false,
duration: animate ? 500 : 0
});
}
/**
* Enables/disables the filter indicating a video problem to the user.
*
* @param enable <tt>true</tt> to enable, <tt>false</tt> to disable
*/
enableVideoProblemFilter (enable) {
let container = this.getContainer(this.state);
container.$video.toggleClass("videoProblemFilter", enable);
}
/**
* Updates the src of the dominant speaker avatar
*/
updateAvatar (avatarUrl) {
$("#dominantSpeakerAvatar").attr('src', avatarUrl);
}
/**
* Show or hide watermark.
* @param {boolean} show
*/
showWatermark (show) {
$('.watermark').css('visibility', show ? 'visible' : 'hidden');
}
/**
* Add container of specified type.
* @param {string} type container type
* @param {LargeContainer} container container to add.
*/
addContainer (type, container) {
if (this.containers[type]) {
throw new Error(`container of type ${type} already exist`);
}
this.containers[type] = container;
this.resizeContainer(type);
}
/**
* Get Large container of specified type.
* @param {string} type container type.
* @returns {LargeContainer}
*/
getContainer (type) {
let container = this.containers[type];
if (!container) {
throw new Error(`container of type ${type} doesn't exist`);
}
return container;
}
/**
* Remove Large container of specified type.
* @param {string} type container type.
*/
removeContainer (type) {
if (!this.containers[type]) {
throw new Error(`container of type ${type} doesn't exist`);
}
delete this.containers[type];
}
/**
* Show Large container of specified type.
* Does nothing if such container is already visible.
* @param {string} type container type.
* @returns {Promise}
*/
showContainer (type) {
if (this.state === type) {
return Promise.resolve();
}
let oldContainer = this.containers[this.state];
if (this.state === VIDEO_CONTAINER_TYPE) {
this.showWatermark(false);
}
oldContainer.hide();
this.state = type;
let container = this.getContainer(type);
return container.show().then(() => {
if (type === VIDEO_CONTAINER_TYPE) {
this.showWatermark(true);
}
});
}
/**
* Changes the flipX state of the local video.
* @param val {boolean} true if flipped.
*/
onLocalFlipXChange(val) {
this.videoContainer.setLocalFlipX(val);
}
}

View File

@@ -8,7 +8,8 @@ import UIEvents from "../../../service/UI/UIEvents";
import UIUtil from "../util/UIUtil";
import RemoteVideo from "./RemoteVideo";
import LargeVideoManager, {VIDEO_CONTAINER_TYPE} from "./LargeVideo";
import LargeVideoManager from "./LargeVideoManager";
import {VIDEO_CONTAINER_TYPE} from "./VideoContainer";
import {SHARED_VIDEO_CONTAINER_TYPE} from '../shared_video/SharedVideo';
import LocalVideo from "./LocalVideo";
@@ -102,19 +103,20 @@ var VideoLayout = {
});
localVideoThumbnail = new LocalVideo(VideoLayout, emitter);
// sets default video type of local video
// FIXME container type is totally different thing from the video type
localVideoThumbnail.setVideoType(VIDEO_CONTAINER_TYPE);
// if we do not resize the thumbs here, if there is no video device
// the local video thumb maybe one pixel
let {thumbWidth, thumbHeight}
= this.resizeThumbnails(false, true, false);
AudioLevels.updateAudioLevelCanvas(null, thumbWidth, thumbHeight);
let { localVideo } = this.resizeThumbnails(false, true);
AudioLevels.createAudioLevelCanvas(
"local", localVideo.thumbWidth, localVideo.thumbHeight);
emitter.addListener(UIEvents.CONTACT_CLICKED, onContactClicked);
this.lastNCount = config.channelLastN;
},
initLargeVideo () {
largeVideo = new LargeVideoManager();
largeVideo = new LargeVideoManager(eventEmitter);
if(localFlipX) {
largeVideo.onLocalFlipXChange(localFlipX);
}
@@ -255,7 +257,8 @@ var VideoLayout = {
electLastVisibleVideo () {
// pick the last visible video in the row
// if nobody else is left, this picks the local video
let thumbs = FilmStrip.getThumbs(true).filter('[id!="mixedstream"]');
let remoteThumbs = FilmStrip.getThumbs(true).remoteThumbs;
let thumbs = remoteThumbs.filter('[id!="mixedstream"]');
let lastVisible = thumbs.filter(':visible:last');
if (lastVisible.length) {
@@ -269,7 +272,7 @@ var VideoLayout = {
}
console.info("Last visible video no longer exists");
thumbs = FilmStrip.getThumbs();
thumbs = FilmStrip.getThumbs().remoteThumbs;
if (thumbs.length) {
let id = getPeerContainerResourceId(thumbs[0]);
if (remoteVideos[id]) {
@@ -396,13 +399,14 @@ var VideoLayout = {
let videoType = VideoLayout.getRemoteVideoType(id);
if (!videoType) {
// make video type the default one (camera)
// FIXME container type is not a video type
videoType = VIDEO_CONTAINER_TYPE;
}
remoteVideo.setVideoType(videoType);
// In case this is not currently in the last n we don't show it.
if (localLastNCount && localLastNCount > 0 &&
FilmStrip.getThumbs().length >= localLastNCount + 2) {
FilmStrip.getThumbs().remoteThumbs.length >= localLastNCount + 2) {
remoteVideo.showPeerContainer('hide');
} else {
VideoLayout.resizeThumbnails(false, true);
@@ -414,7 +418,7 @@ var VideoLayout = {
console.info(resourceJid + " video is now active", videoelem);
VideoLayout.resizeThumbnails(
false, false, false, function() {$(videoelem).show();});
false, false, function() {$(videoelem).show();});
// Update the large video to the last added video only if there's no
// current dominant, focused speaker or update it to
@@ -487,19 +491,19 @@ var VideoLayout = {
forceUpdate = false,
onComplete = null) {
let {thumbWidth, thumbHeight}
let { localVideo, remoteVideo }
= FilmStrip.calculateThumbnailSize();
$('.userAvatar').css('left', (thumbWidth - thumbHeight) / 2);
let {thumbWidth, thumbHeight} = remoteVideo;
FilmStrip.resizeThumbnails(thumbWidth, thumbHeight,
FilmStrip.resizeThumbnails(localVideo, remoteVideo,
animate, forceUpdate)
.then(function () {
AudioLevels.updateCanvasSize(thumbWidth, thumbHeight);
AudioLevels.updateCanvasSize(localVideo, remoteVideo);
if (onComplete && typeof onComplete === "function")
onComplete();
});
return {thumbWidth, thumbHeight};
});
return { localVideo, remoteVideo };
},
/**
@@ -657,7 +661,7 @@ var VideoLayout = {
var updateLargeVideo = false;
// Handle LastN/local LastN changes.
FilmStrip.getThumbs().each(( index, element ) => {
FilmStrip.getThumbs().remoteThumbs.each(( index, element ) => {
var resourceJid = getPeerContainerResourceId(element);
var smallVideo = remoteVideos[resourceJid];
@@ -995,6 +999,7 @@ var VideoLayout = {
if (!isOnLarge || forceUpdate) {
let videoType = this.getRemoteVideoType(id);
// FIXME video type is not the same thing as container type
if (id !== currentId && videoType === VIDEO_CONTAINER_TYPE) {
eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, id);
}

View File

@@ -2,6 +2,7 @@
var animateTimeout, updateTimeout;
var RoomnameGenerator = require("../../util/RoomnameGenerator");
import UIUtil from "../util/UIUtil";
function enter_room() {
var val = $("#enter_room_field").val();
@@ -39,10 +40,10 @@ function setupWelcomePage() {
$("#welcome_page_header div[class='watermark leftwatermark']");
if(leftWatermarkDiv && leftWatermarkDiv.length > 0) {
leftWatermarkDiv.css({display: 'block'});
leftWatermarkDiv.parent().get(0).href =
interfaceConfig.JITSI_WATERMARK_LINK;
UIUtil.setLinkHref(
leftWatermarkDiv.parent(),
interfaceConfig.JITSI_WATERMARK_LINK);
}
}
if (interfaceConfig.SHOW_BRAND_WATERMARK) {
@@ -50,8 +51,9 @@ function setupWelcomePage() {
$("#welcome_page_header div[class='watermark rightwatermark']");
if(rightWatermarkDiv && rightWatermarkDiv.length > 0) {
rightWatermarkDiv.css({display: 'block'});
rightWatermarkDiv.parent().get(0).href =
interfaceConfig.BRAND_WATERMARK_LINK;
UIUtil.setLinkHref(
rightWatermarkDiv.parent(),
interfaceConfig.BRAND_WATERMARK_LINK);
rightWatermarkDiv.get(0).style.backgroundImage =
"url(images/rightwatermark.png)";
}

View File

@@ -17,11 +17,6 @@ function initGlobalShortcuts() {
APP.UI.toggleKeyboardShortcutsPanel();
}, "keyboardShortcuts.toggleShortcuts");
KeyboardShortcut.registerShortcut("R", null, function() {
JitsiMeetJS.analytics.sendEvent("shortcut.raisedhand.toggled");
APP.conference.maybeToggleRaisedHand();
}, "keyboardShortcuts.raiseHand");
KeyboardShortcut.registerShortcut("T", null, function() {
JitsiMeetJS.analytics.sendEvent("shortcut.talk.clicked");
APP.conference.muteAudio(true);
@@ -79,13 +74,6 @@ var KeyboardShortcut = {
}
}
};
$('body').popover({ selector: '[data-toggle=popover]',
trigger: 'click hover',
content: function() {
return this.getAttribute("content")
+ self._getShortcutTooltip(this.getAttribute("shortcut"));
}
});
},
/**
@@ -133,7 +121,7 @@ var KeyboardShortcut = {
* or an empty string if the shortcutAttr is null, an empty string or not
* found in the shortcut mapping
*/
_getShortcutTooltip: function (shortcutAttr) {
getShortcutTooltip: function (shortcutAttr) {
if (typeof shortcutAttr === "string" && shortcutAttr.length > 0) {
for (var key in _shortcuts) {
if (_shortcuts.hasOwnProperty(key)

View File

@@ -174,10 +174,12 @@ export default {
* Set device id of the camera which is currently in use.
* Empty string stands for default device.
* @param {string} newId new camera device id
* @param {boolean} whether we need to store the value
*/
setCameraDeviceId: function (newId = '') {
setCameraDeviceId: function (newId, store) {
cameraDeviceId = newId;
window.localStorage.cameraDeviceId = newId;
if (store)
window.localStorage.cameraDeviceId = newId;
},
/**
@@ -192,10 +194,12 @@ export default {
* Set device id of the microphone which is currently in use.
* Empty string stands for default device.
* @param {string} newId new microphone device id
* @param {boolean} whether we need to store the value
*/
setMicDeviceId: function (newId = '') {
setMicDeviceId: function (newId, store) {
micDeviceId = newId;
window.localStorage.micDeviceId = newId;
if (store)
window.localStorage.micDeviceId = newId;
},
/**

View File

@@ -16,23 +16,24 @@
"readmeFilename": "README.md",
"//": "Callstats.io does not work with recent versions of jsSHA (2.0.1 in particular)",
"dependencies": {
"@atlassian/aui": "^6.0.0",
"async": "0.9.0",
"autosize": "^1.18.13",
"bootstrap": "3.1.1",
"events": "*",
"i18next-client": "1.7.7",
"jquery": "~2.1.1",
"jQuery-Impromptu": "git+https://github.com/trentrichardson/jQuery-Impromptu.git#v6.0.0",
"lib-jitsi-meet": "git+https://github.com/jitsi/lib-jitsi-meet.git",
"jquery": "~2.1.1",
"jquery-contextmenu": "*",
"jquery-ui": "1.10.5",
"jssha": "1.5.0",
"jws": "*",
"lib-jitsi-meet": "git+https://github.com/jitsi/lib-jitsi-meet.git",
"postis": "^2.2.0",
"retry": "0.6.1",
"strophe": "^1.2.2",
"strophejs-plugins": "^0.0.6",
"toastr": "^2.0.3",
"postis": "^2.2.0",
"jws": "*"
"toastr": "^2.0.3"
},
"devDependencies": {
"babel-polyfill": "*",
@@ -83,7 +84,11 @@
"tooltip": "./node_modules/bootstrap/js/tooltip.js",
"popover": "./node_modules/bootstrap/js/popover.js",
"jQuery-Impromptu": "./node_modules/jQuery-Impromptu/dist/jquery-impromptu.js",
"autosize": "./node_modules/autosize/build/jquery.autosize.js"
"autosize": "./node_modules/autosize/build/jquery.autosize.js",
"aui": "./node_modules/@atlassian/aui/dist/aui/js/aui.js",
"aui-experimental": "./node_modules/@atlassian/aui/dist/aui/js/aui-experimental.js",
"aui-css": "./node_modules/@atlassian/aui/dist/aui/css/aui.min.css",
"aui-experimental-css": "./node_modules/@atlassian/aui/dist/aui/css/aui-experimental.min.css"
},
"browserify-shim": {
"jquery": [
@@ -109,6 +114,9 @@
"jQuery-Impromptu": {
"depends": "jquery:jQuery"
},
"aui-experimental": {
"depends": "aui:AJS"
},
"jquery-contextmenu": {
"depends": "jquery:jQuery"
},

View File

@@ -105,5 +105,15 @@ export default {
* event must contain the identifier of the container that has been toggled
* and information about toggle on or off.
*/
SIDE_TOOLBAR_CONTAINER_TOGGLED: "UI.side_container_toggled"
SIDE_TOOLBAR_CONTAINER_TOGGLED: "UI.side_container_toggled",
/**
* Notifies that the raise hand has been changed.
*/
LOCAL_RAISE_HAND_CHANGED: "UI.local_raise_hand_changed",
/**
* Notifies that the avatar is displayed or not on the largeVideo.
*/
LARGE_VIDEO_AVATAR_DISPLAYED: "UI.large_video_avatar_displayed"
};