feat(eslint): Enable for non react files

This commit is contained in:
hristoterezov 2017-10-12 18:02:29 -05:00 committed by Lyubo Marinov
parent b1b3807e9b
commit 969f5d67ab
55 changed files with 3612 additions and 2711 deletions

View File

@ -23,9 +23,157 @@ module.exports = {
'sourceType': 'module' 'sourceType': 'module'
}, },
'plugins': [ 'plugins': [
'flowtype' 'flowtype',
// ESLint's rule no-duplicate-imports does not understand Flow's import
// type. Fortunately, eslint-plugin-import understands Flow's import
// type.
'import'
], ],
'rules': { 'rules': {
// Possible Errors group
'no-cond-assign': 2,
'no-console': 0,
'no-constant-condition': 2,
'no-control-regex': 2,
'no-debugger': 2,
'no-dupe-args': 2,
'no-dupe-keys': 2,
'no-duplicate-case': 2,
'no-empty': 2,
'no-empty-character-class': 2,
'no-ex-assign': 2,
'no-extra-boolean-cast': 2,
'no-extra-parens': [
'error',
'all',
{ 'nestedBinaryExpressions': false }
],
'no-extra-semi': 2,
'no-func-assign': 2,
'no-inner-declarations': 2,
'no-invalid-regexp': 2,
'no-irregular-whitespace': 2,
'no-negated-in-lhs': 2,
'no-obj-calls': 2,
'no-prototype-builtins': 0,
'no-regex-spaces': 2,
'no-sparse-arrays': 2,
'no-unexpected-multiline': 2,
'no-unreachable': 2,
'no-unsafe-finally': 2,
'use-isnan': 2,
'valid-typeof': 2,
// Best Practices group
'accessor-pairs': 0,
'array-callback-return': 2,
'block-scoped-var': 0,
'complexity': 0,
'consistent-return': 0,
'curly': 2,
'default-case': 0,
'dot-location': [ 'error', 'property' ],
'dot-notation': 2,
'eqeqeq': 2,
'guard-for-in': 2,
'no-alert': 2,
'no-caller': 2,
'no-case-declarations': 2,
'no-div-regex': 0,
'no-else-return': 2,
'no-empty-function': 2,
'no-empty-pattern': 2,
'no-eq-null': 2,
'no-eval': 2,
'no-extend-native': 2,
'no-extra-bind': 2,
'no-extra-label': 2,
'no-fallthrough': 2,
'no-floating-decimal': 2,
'no-implicit-coercion': 2,
'no-implicit-globals': 2,
'no-implied-eval': 2,
'no-invalid-this': 2,
'no-iterator': 2,
'no-labels': 2,
'no-lone-blocks': 2,
'no-loop-func': 2,
'no-magic-numbers': 0,
'no-multi-spaces': 2,
'no-multi-str': 2,
'no-native-reassign': 2,
'no-new': 2,
'no-new-func': 2,
'no-new-wrappers': 2,
'no-octal': 2,
'no-octal-escape': 2,
'no-param-reassign': 2,
'no-proto': 2,
'no-redeclare': 2,
'no-return-assign': 2,
'no-script-url': 2,
'no-self-assign': 2,
'no-self-compare': 2,
'no-sequences': 2,
'no-throw-literal': 2,
'no-unmodified-loop-condition': 2,
'no-unused-expressions': [
'error',
{
'allowShortCircuit': true,
'allowTernary': true
}
],
'no-unused-labels': 2,
'no-useless-call': 2,
'no-useless-concat': 2,
'no-useless-escape': 2,
'no-void': 2,
'no-warning-comments': 0,
'no-with': 2,
'radix': 2,
'vars-on-top': 2,
'wrap-iife': [ 'error', 'inside' ],
'yoda': 2,
// Strict Mode group
'strict': 2,
// Variables group
'init-declarations': 0,
'no-catch-shadow': 2,
'no-delete-var': 2,
'no-label-var': 2,
'no-restricted-globals': 0,
'no-shadow': 2,
'no-shadow-restricted-names': 2,
'no-undef': 2,
'no-undef-init': 2,
'no-undefined': 0,
'no-unused-vars': 2,
'no-use-before-define': [ 'error', { 'functions': false } ],
// Stylistic issues group
'array-bracket-spacing': [
'error',
'always',
{ 'objectsInArrays': true }
],
'block-spacing': [ 'error', 'always' ],
'brace-style': 2,
'camelcase': 2,
'comma-dangle': 2,
'comma-spacing': 2,
'comma-style': 2,
'computed-property-spacing': 2,
'consistent-this': [ 'error', 'self' ],
'eol-last': 2,
'func-names': 0,
'func-style': 0,
'id-blacklist': 0,
'id-length': 0,
'id-match': 0,
'indent': [ 'indent': [
'error', 'error',
4, 4,
@ -43,19 +191,121 @@ module.exports = {
'SwitchCase': 0 'SwitchCase': 0
} }
], ],
'new-cap': [ 'key-spacing': 2,
'keyword-spacing': 2,
'linebreak-style': [ 'error', 'unix' ],
'lines-around-comment': [
'error', 'error',
{ {
'capIsNew': false // Behave like JSHint's newcap. 'allowBlockStart': true,
'allowObjectStart': true,
'beforeBlockComment': true,
'beforeLineComment': true
} }
], ],
// While it is considered a best practice to avoid using methods on 'max-depth': 2,
// console in JavaScript that is designed to be executed in the browser 'max-len': [ 'error', 80 ],
// and ESLint includes the rule among its set of recommended rules, (1) 'max-lines': 0,
// the general practice is to strip such calls before pushing to 'max-nested-callbacks': 2,
// production and (2) we prefer to utilize console in lib-jitsi-meet 'max-params': 2,
// (and jitsi-meet). 'max-statements': 0,
'no-console': 'off', 'max-statements-per-line': 2,
'semi': 'error' 'multiline-ternary': 0,
'new-cap': 2,
'new-parens': 2,
'newline-after-var': 2,
'newline-before-return': 2,
'newline-per-chained-call': 2,
'no-array-constructor': 2,
'no-bitwise': 2,
'no-continue': 2,
'no-inline-comments': 0,
'no-lonely-if': 2,
'no-mixed-operators': 2,
'no-mixed-spaces-and-tabs': 2,
'no-multiple-empty-lines': 2,
'no-negated-condition': 2,
'no-nested-ternary': 0,
'no-new-object': 2,
'no-plusplus': 0,
'no-restricted-syntax': 0,
'no-spaced-func': 2,
'no-tabs': 2,
'no-ternary': 0,
'no-trailing-spaces': 2,
'no-underscore-dangle': 0,
'no-unneeded-ternary': 2,
'no-whitespace-before-property': 2,
'object-curly-newline': 0,
'object-curly-spacing': [ 'error', 'always' ],
'object-property-newline': 2,
'one-var': 0,
'one-var-declaration-per-line': 0,
'operator-assignment': 0,
'operator-linebreak': [ 'error', 'before' ],
'padded-blocks': 0,
'quote-props': 0,
'quotes': [ 'error', 'single' ],
'require-jsdoc': [
'error',
{
'require': {
'ClassDeclaration': true,
'FunctionDeclaration': true,
'MethodDefinition': true
}
}
],
'semi': [ 'error', 'always' ],
'semi-spacing': 2,
'sort-vars': 2,
'space-before-blocks': 2,
'space-before-function-paren': [ 'error', 'never' ],
'space-in-parens': [ 'error', 'never' ],
'space-infix-ops': 2,
'space-unary-ops': 2,
'spaced-comment': 2,
'unicode-bom': 0,
'wrap-regex': 0,
// ES6 group rules
'arrow-body-style': [
'error',
'as-needed',
{ requireReturnForObjectLiteral: true }
],
'arrow-parens': [ 'error', 'as-needed' ],
'arrow-spacing': 2,
'constructor-super': 2,
'generator-star-spacing': 2,
'no-class-assign': 2,
'no-confusing-arrow': 2,
'no-const-assign': 2,
'no-dupe-class-members': 2,
'no-new-symbol': 2,
'no-restricted-imports': 0,
'no-this-before-super': 2,
'no-useless-computed-key': 2,
'no-useless-constructor': 2,
'no-useless-rename': 2,
'no-var': 2,
'object-shorthand': [
'error',
'always',
{ 'avoidQuotes': true }
],
'prefer-arrow-callback': [ 'error', { 'allowNamedFunctions': true } ],
'prefer-const': 2,
'prefer-reflect': 0,
'prefer-rest-params': 2,
'prefer-spread': 2,
'prefer-template': 2,
'require-yield': 2,
'rest-spread-spacing': 2,
'sort-imports': 0,
'template-curly-spacing': 2,
'yield-star-spacing': 2,
'import/no-duplicates': 2
} }
}; };

View File

@ -1,11 +1,11 @@
/** /**
* Notifies interested parties that hangup procedure will start. * Notifies interested parties that hangup procedure will start.
*/ */
export const BEFORE_HANGUP = "conference.before_hangup"; export const BEFORE_HANGUP = 'conference.before_hangup';
/** /**
* Notifies interested parties that desktop sharing enable/disable state is * Notifies interested parties that desktop sharing enable/disable state is
* changed. * changed.
*/ */
export const DESKTOP_SHARING_ENABLED_CHANGED export const DESKTOP_SHARING_ENABLED_CHANGED
= "conference.desktop_sharing_enabled_changed"; = 'conference.desktop_sharing_enabled_changed';

View File

@ -1,38 +1,45 @@
/* global ga */ /* global ga */
/* eslint-disable indent */ /* eslint-disable indent */
(function (ctx) { (function(ctx) {
function Analytics() {
/* eslint-disable semi */
/** /**
* Google Analytics *
*/ */
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ function Analytics() {
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) /* eslint-disable */
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-319188-14', 'jit.si');
ga('send', 'pageview');
/* eslint-enable semi */ /**
} * Google Analytics
*/
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-319188-14', 'jit.si');
ga('send', 'pageview');
Analytics.prototype.sendEvent = function (action, data) { /* eslint-enable */
// empty label if missing value for it and add the value, }
// the value should be integer or null
var value = data.value;
value = value? Math.round(parseFloat(value)) : null;
var label = data.label || "";
ga('send', 'event', 'jit.si', Analytics.prototype.sendEvent = function(action, data) {
action + '.' + data.browserName, label, value); // empty label if missing value for it and add the value,
}; // the value should be integer or null
let value = data.value;
if (typeof ctx.JitsiMeetJS === "undefined") value = value ? Math.round(parseFloat(value)) : null;
ctx.JitsiMeetJS = {}; const label = data.label || '';
if (typeof ctx.JitsiMeetJS.app === "undefined")
ctx.JitsiMeetJS.app = {}; ga('send', 'event', 'jit.si',
if (typeof ctx.JitsiMeetJS.app.analyticsHandlers === "undefined") `${action}.${data.browserName}`, label, value);
ctx.JitsiMeetJS.app.analyticsHandlers = []; };
ctx.JitsiMeetJS.app.analyticsHandlers.push(Analytics);
}(window)); if (typeof ctx.JitsiMeetJS === 'undefined') {
ctx.JitsiMeetJS = {};
}
if (typeof ctx.JitsiMeetJS.app === 'undefined') {
ctx.JitsiMeetJS.app = {};
}
if (typeof ctx.JitsiMeetJS.app.analyticsHandlers === 'undefined') {
ctx.JitsiMeetJS.app.analyticsHandlers = [];
}
ctx.JitsiMeetJS.app.analyticsHandlers.push(Analytics);
})(window);

File diff suppressed because it is too large Load Diff

105
config.js
View File

@ -1,12 +1,13 @@
/* eslint-disable no-unused-vars, no-var */
var config = { // eslint-disable-line no-unused-vars var config = { // eslint-disable-line no-unused-vars
// Configuration // Configuration
// //
// Alternative location for the configuration. // Alternative location for the configuration.
//configLocation: './config.json', // configLocation: './config.json',
// Custom function which given the URL path should return a room name. // Custom function which given the URL path should return a room name.
//getroomnode: function (path) { return 'someprefixpossiblybasedonpath'; }, // getroomnode: function (path) { return 'someprefixpossiblybasedonpath'; },
// Connection // Connection
@ -17,22 +18,22 @@ var config = { // eslint-disable-line no-unused-vars
domain: 'jitsi-meet.example.com', domain: 'jitsi-meet.example.com',
// XMPP MUC domain. FIXME: use XEP-0030 to discover it. // XMPP MUC domain. FIXME: use XEP-0030 to discover it.
muc: 'conference.jitsi-meet.example.com', muc: 'conference.jitsi-meet.example.com'
// When using authentication, domain for guest users. // When using authentication, domain for guest users.
//anonymousdomain: 'guest.example.com', // anonymousdomain: 'guest.example.com',
// Domain for authenticated users. Defaults to <domain>. // Domain for authenticated users. Defaults to <domain>.
//authdomain: 'jitsi-meet.example.com', // authdomain: 'jitsi-meet.example.com',
// Jirecon recording component domain. // Jirecon recording component domain.
//jirecon: 'jirecon.jitsi-meet.example.com', // jirecon: 'jirecon.jitsi-meet.example.com',
// Call control component (Jigasi). // Call control component (Jigasi).
//call_control: 'callcontrol.jitsi-meet.example.com', // call_control: 'callcontrol.jitsi-meet.example.com',
// Focus component domain. Defaults to focus.<domain>. // Focus component domain. Defaults to focus.<domain>.
//focus: 'focus.jitsi-meet.example.com', // focus: 'focus.jitsi-meet.example.com',
}, },
// BOSH URL. FIXME: use XEP-0156 to discover it. // BOSH URL. FIXME: use XEP-0156 to discover it.
@ -42,7 +43,7 @@ var config = { // eslint-disable-line no-unused-vars
clientNode: 'http://jitsi.org/jitsimeet', clientNode: 'http://jitsi.org/jitsimeet',
// The real JID of focus participant - can be overridden here // The real JID of focus participant - can be overridden here
//focusUserJid: 'focus@auth.jitsi-meet.example.com', // focusUserJid: 'focus@auth.jitsi-meet.example.com',
// Testing / experimental features. // Testing / experimental features.
@ -51,18 +52,19 @@ var config = { // eslint-disable-line no-unused-vars
testing: { testing: {
// Enables experimental simulcast support on Firefox. // Enables experimental simulcast support on Firefox.
enableFirefoxSimulcast: false, enableFirefoxSimulcast: false,
// P2P test mode disables automatic switching to P2P when there are 2 // P2P test mode disables automatic switching to P2P when there are 2
// participants in the conference. // participants in the conference.
p2pTestMode: false, p2pTestMode: false
}, },
// Disables ICE/UDP by filtering out local and remote UDP candidates in // Disables ICE/UDP by filtering out local and remote UDP candidates in
// signalling. // signalling.
//webrtcIceUdpDisable: false, // webrtcIceUdpDisable: false,
// Disables ICE/TCP by filtering out local and remote TCP candidates in // Disables ICE/TCP by filtering out local and remote TCP candidates in
// signalling. // signalling.
//webrtcIceTcpDisable: false, // webrtcIceTcpDisable: false,
// Media // Media
@ -71,52 +73,52 @@ var config = { // eslint-disable-line no-unused-vars
// Audio // Audio
// Disable measuring of audio levels. // Disable measuring of audio levels.
//disableAudioLevels: false, // disableAudioLevels: false,
// Start the conference in audio only mode (no video is being received nor // Start the conference in audio only mode (no video is being received nor
// sent). // sent).
//startAudioOnly: false, // startAudioOnly: false,
// Every participant after the Nth will start audio muted. // Every participant after the Nth will start audio muted.
//startAudioMuted: 10, // startAudioMuted: 10,
// Start calls with audio muted. Unlike the option above, this one is only // Start calls with audio muted. Unlike the option above, this one is only
// applied locally. FIXME: having these 2 options is confusing. // applied locally. FIXME: having these 2 options is confusing.
//startWithAudioMuted: false, // startWithAudioMuted: false,
// Video // Video
// Sets the preferred resolution (height) for local video. Defaults to 720. // Sets the preferred resolution (height) for local video. Defaults to 720.
//resolution: 720, // resolution: 720,
// Enable / disable simulcast support. // Enable / disable simulcast support.
//disableSimulcast: false, // disableSimulcast: false,
// Suspend sending video if bandwidth estimation is too low. This may cause // Suspend sending video if bandwidth estimation is too low. This may cause
// problems with audio playback. Disabled until these are fixed. // problems with audio playback. Disabled until these are fixed.
disableSuspendVideo: true, disableSuspendVideo: true,
// Every participant after the Nth will start video muted. // Every participant after the Nth will start video muted.
//startVideoMuted: 10, // startVideoMuted: 10,
// Start calls with video muted. Unlike the option above, this one is only // Start calls with video muted. Unlike the option above, this one is only
// applied locally. FIXME: having these 2 options is confusing. // applied locally. FIXME: having these 2 options is confusing.
//startWithVideoMuted: false, // startWithVideoMuted: false,
// If set to true, prefer to use the H.264 video codec (if supported). // If set to true, prefer to use the H.264 video codec (if supported).
// Note that it's not recommended to do this because simulcast is not // Note that it's not recommended to do this because simulcast is not
// supported when using H.264. For 1-to-1 calls this setting is enabled by // supported when using H.264. For 1-to-1 calls this setting is enabled by
// default and can be toggled in the p2p section. // default and can be toggled in the p2p section.
//preferH264: true, // preferH264: true,
// If set to true, disable H.264 video codec by stripping it out of the // If set to true, disable H.264 video codec by stripping it out of the
// SDP. // SDP.
//disableH264: false, // disableH264: false,
// Desktop sharing // Desktop sharing
// Enable / disable desktop sharing // Enable / disable desktop sharing
//disableDesktopSharing: false, // disableDesktopSharing: false,
// The ID of the jidesha extension for Chrome. // The ID of the jidesha extension for Chrome.
desktopSharingChromeExtId: null, desktopSharingChromeExtId: null,
@ -126,7 +128,7 @@ var config = { // eslint-disable-line no-unused-vars
// The media sources to use when using screen sharing with the Chrome // The media sources to use when using screen sharing with the Chrome
// extension. // extension.
desktopSharingChromeSources: ['screen', 'window', 'tab'], desktopSharingChromeSources: [ 'screen', 'window', 'tab' ],
// Required version of Chrome extension // Required version of Chrome extension
desktopSharingChromeMinExtVersion: '0.1', desktopSharingChromeMinExtVersion: '0.1',
@ -149,15 +151,15 @@ var config = { // eslint-disable-line no-unused-vars
desktopSharingFirefoxExtensionURL: null, desktopSharingFirefoxExtensionURL: null,
// Try to start calls with screen-sharing instead of camera video. // Try to start calls with screen-sharing instead of camera video.
//startScreenSharing: false, // startScreenSharing: false,
// Recording // Recording
// Whether to enable recording or not. // Whether to enable recording or not.
//enableRecording: false, // enableRecording: false,
// Type for recording: one of jibri or jirecon. // Type for recording: one of jibri or jirecon.
//recordingType: 'jibri', // recordingType: 'jibri',
// Misc // Misc
@ -165,29 +167,29 @@ var config = { // eslint-disable-line no-unused-vars
channelLastN: -1, channelLastN: -1,
// Disables or enables RTX (RFC 4588) (defaults to false). // Disables or enables RTX (RFC 4588) (defaults to false).
//disableRtx: false, // disableRtx: false,
// Use XEP-0215 to fetch STUN and TURN servers. // Use XEP-0215 to fetch STUN and TURN servers.
//useStunTurn: true, // useStunTurn: true,
// Enable IPv6 support. // Enable IPv6 support.
//useIPv6: true, // useIPv6: true,
// Enables / disables a data communication channel with the Videobridge. // Enables / disables a data communication channel with the Videobridge.
// Values can be 'datachannel', 'websocket', true (treat it as // Values can be 'datachannel', 'websocket', true (treat it as
// 'datachannel'), undefined (treat it as 'datachannel') and false (don't // 'datachannel'), undefined (treat it as 'datachannel') and false (don't
// open any channel). // open any channel).
//openBridgeChannel: true, // openBridgeChannel: true,
// UI // UI
// //
// Use display name as XMPP nickname. // Use display name as XMPP nickname.
//useNicks: false, // useNicks: false,
// Require users to always specify a display name. // Require users to always specify a display name.
//requireDisplayName: true, // requireDisplayName: true,
// Whether to use a welcome page or not. In case it's false a random room // Whether to use a welcome page or not. In case it's false a random room
// will be joined when no room is specified. // will be joined when no room is specified.
@ -195,17 +197,19 @@ var config = { // eslint-disable-line no-unused-vars
// Enabling the close page will ignore the welcome page redirection when // Enabling the close page will ignore the welcome page redirection when
// a call is hangup. // a call is hangup.
//enableClosePage: false, // enableClosePage: false,
// Disable hiding of remote thumbnails when in a 1-on-1 conference call. // Disable hiding of remote thumbnails when in a 1-on-1 conference call.
//disable1On1Mode: false, // disable1On1Mode: false,
// The minumum value a video's height (or width, whichever is smaller) needs
// The minimum value a video's height (or width, whichever is smaller) needs // The minimum value a video's height (or width, whichever is smaller) needs
// to be in order to be considered high-definition. // to be in order to be considered high-definition.
minHDHeight: 540, minHDHeight: 540,
// Default language for the user interface. // Default language for the user interface.
//defaultLanguage: 'en', // defaultLanguage: 'en',
// If true all users without a token will be considered guests and all users // If true all users without a token will be considered guests and all users
// with token will be considered non-guests. Only guests will be allowed to // with token will be considered non-guests. Only guests will be allowed to
@ -214,19 +218,19 @@ var config = { // eslint-disable-line no-unused-vars
// Message to show the users. Example: 'The service will be down for // Message to show the users. Example: 'The service will be down for
// maintenance at 01:00 AM GMT, // maintenance at 01:00 AM GMT,
//noticeMessage: '', // noticeMessage: '',
// Stats // Stats
// //
// Whether to enable stats collection or not. // Whether to enable stats collection or not.
//disableStats: false, // disableStats: false,
// To enable sending statistics to callstats.io you must provide the // To enable sending statistics to callstats.io you must provide the
// Application ID and Secret. // Application ID and Secret.
//callStatsID: '', // callStatsID: '',
//callStatsSecret: '', // callStatsSecret: '',
// Privacy // Privacy
@ -235,7 +239,7 @@ var config = { // eslint-disable-line no-unused-vars
// If third party requests are disabled, no other server will be contacted. // If third party requests are disabled, no other server will be contacted.
// This means avatars will be locally generated and callstats integration // This means avatars will be locally generated and callstats integration
// will not function. // will not function.
//disableThirdPartyRequests: false, // disableThirdPartyRequests: false,
// Peer-To-Peer mode: used (if enabled) when there are just 2 participants. // Peer-To-Peer mode: used (if enabled) when there are just 2 participants.
@ -251,13 +255,13 @@ var config = { // eslint-disable-line no-unused-vars
enabled: true, enabled: true,
// Use XEP-0215 to fetch STUN and TURN servers. // Use XEP-0215 to fetch STUN and TURN servers.
//useStunTurn: true, // useStunTurn: true,
// The STUN servers that will be used in the peer to peer connections // The STUN servers that will be used in the peer to peer connections
stunServers: [ stunServers: [
{ urls: "stun:stun.l.google.com:19302" }, { urls: 'stun:stun.l.google.com:19302' },
{ urls: "stun:stun1.l.google.com:19302" }, { urls: 'stun:stun1.l.google.com:19302' },
{ urls: "stun:stun2.l.google.com:19302" } { urls: 'stun:stun2.l.google.com:19302' }
], ],
// If set to true, it will prefer to use H.264 for P2P calls (if H.264 // If set to true, it will prefer to use H.264 for P2P calls (if H.264
@ -266,11 +270,11 @@ var config = { // eslint-disable-line no-unused-vars
// If set to true, disable H.264 video codec by stripping it out of the // If set to true, disable H.264 video codec by stripping it out of the
// SDP. // SDP.
//disableH264: false, // disableH264: false,
// How long we're going to wait, before going back to P2P after the 3rd // How long we're going to wait, before going back to P2P after the 3rd
// participant has left the conference (to filter out page reload). // participant has left the conference (to filter out page reload).
//backToP2PDelay: 5 // backToP2PDelay: 5
}, },
@ -279,8 +283,9 @@ var config = { // eslint-disable-line no-unused-vars
// //
deploymentInfo: { deploymentInfo: {
//shard: "shard1", // shard: "shard1",
//region: "europe", // region: "europe",
//userRegion: "asia" // userRegion: "asia"
} }
}; };
/* eslint-enable no-unused-vars, no-var */

View File

@ -13,7 +13,7 @@ import {
JitsiConnectionEvents JitsiConnectionEvents
} from './react/features/base/lib-jitsi-meet'; } from './react/features/base/lib-jitsi-meet';
const logger = require("jitsi-meet-logger").getLogger(__filename); const logger = require('jitsi-meet-logger').getLogger(__filename);
/** /**
* Checks if we have data to use attach instead of connect. If we have the data * Checks if we have data to use attach instead of connect. If we have the data
@ -27,26 +27,31 @@ const logger = require("jitsi-meet-logger").getLogger(__filename);
* @param {string} [roomName] the name of the conference. * @param {string} [roomName] the name of the conference.
*/ */
function checkForAttachParametersAndConnect(id, password, connection) { function checkForAttachParametersAndConnect(id, password, connection) {
if(window.XMPPAttachInfo){ if (window.XMPPAttachInfo) {
APP.connect.status = "connecting"; APP.connect.status = 'connecting';
// When connection optimization is not deployed or enabled the default // When connection optimization is not deployed or enabled the default
// value will be window.XMPPAttachInfo.status = "error" // value will be window.XMPPAttachInfo.status = "error"
// If the connection optimization is deployed and enabled and there is // If the connection optimization is deployed and enabled and there is
// a failure the value will be window.XMPPAttachInfo.status = "error" // a failure the value will be window.XMPPAttachInfo.status = "error"
if(window.XMPPAttachInfo.status === "error") { if (window.XMPPAttachInfo.status === 'error') {
connection.connect({id, password}); connection.connect({ id,
password });
return; return;
} }
var attachOptions = window.XMPPAttachInfo.data; const attachOptions = window.XMPPAttachInfo.data;
if(attachOptions) {
if (attachOptions) {
connection.attach(attachOptions); connection.attach(attachOptions);
delete window.XMPPAttachInfo.data; delete window.XMPPAttachInfo.data;
} else { } else {
connection.connect({id, password}); connection.connect({ id,
password });
} }
} else { } else {
APP.connect.status = "ready"; APP.connect.status = 'ready';
APP.connect.handler = checkForAttachParametersAndConnect.bind(null, APP.connect.handler = checkForAttachParametersAndConnect.bind(null,
id, password, connection); id, password, connection);
} }
@ -64,15 +69,15 @@ function connect(id, password, roomName) {
const connectionConfig = Object.assign({}, config); const connectionConfig = Object.assign({}, config);
const { issuer, jwt } = APP.store.getState()['features/base/jwt']; const { issuer, jwt } = APP.store.getState()['features/base/jwt'];
connectionConfig.bosh += '?room=' + roomName; connectionConfig.bosh += `?room=${roomName}`;
let connection const connection
= new JitsiMeetJS.JitsiConnection( = new JitsiMeetJS.JitsiConnection(
null, null,
jwt && issuer && issuer !== 'anonymous' ? jwt : undefined, jwt && issuer && issuer !== 'anonymous' ? jwt : undefined,
connectionConfig); connectionConfig);
return new Promise(function (resolve, reject) { return new Promise((resolve, reject) => {
connection.addEventListener( connection.addEventListener(
JitsiConnectionEvents.CONNECTION_ESTABLISHED, JitsiConnectionEvents.CONNECTION_ESTABLISHED,
handleConnectionEstablished); handleConnectionEstablished);
@ -83,6 +88,9 @@ function connect(id, password, roomName) {
JitsiConnectionEvents.CONNECTION_FAILED, JitsiConnectionEvents.CONNECTION_FAILED,
connectionFailedHandler); connectionFailedHandler);
/**
*
*/
function connectionFailedHandler(error, message, credentials) { function connectionFailedHandler(error, message, credentials) {
APP.store.dispatch( APP.store.dispatch(
connectionFailed(connection, error, message, credentials)); connectionFailed(connection, error, message, credentials));
@ -94,6 +102,9 @@ function connect(id, password, roomName) {
} }
} }
/**
*
*/
function unsubscribe() { function unsubscribe() {
connection.removeEventListener( connection.removeEventListener(
JitsiConnectionEvents.CONNECTION_ESTABLISHED, JitsiConnectionEvents.CONNECTION_ESTABLISHED,
@ -103,15 +114,21 @@ function connect(id, password, roomName) {
handleConnectionFailed); handleConnectionFailed);
} }
/**
*
*/
function handleConnectionEstablished() { function handleConnectionEstablished() {
APP.store.dispatch(connectionEstablished(connection)); APP.store.dispatch(connectionEstablished(connection));
unsubscribe(); unsubscribe();
resolve(connection); resolve(connection);
} }
/**
*
*/
function handleConnectionFailed(err) { function handleConnectionFailed(err) {
unsubscribe(); unsubscribe();
logger.error("CONNECTION FAILED:", err); logger.error('CONNECTION FAILED:', err);
reject(err); reject(err);
} }
@ -132,17 +149,17 @@ function connect(id, password, roomName) {
* *
* @returns {Promise<JitsiConnection>} * @returns {Promise<JitsiConnection>}
*/ */
export function openConnection({id, password, retry, roomName}) { export function openConnection({ id, password, retry, roomName }) {
let usernameOverride const usernameOverride
= jitsiLocalStorage.getItem("xmpp_username_override"); = jitsiLocalStorage.getItem('xmpp_username_override');
let passwordOverride const passwordOverride
= jitsiLocalStorage.getItem("xmpp_password_override"); = jitsiLocalStorage.getItem('xmpp_password_override');
if (usernameOverride && usernameOverride.length > 0) { if (usernameOverride && usernameOverride.length > 0) {
id = usernameOverride; id = usernameOverride; // eslint-disable-line no-param-reassign
} }
if (passwordOverride && passwordOverride.length > 0) { if (passwordOverride && passwordOverride.length > 0) {
password = passwordOverride; password = passwordOverride; // eslint-disable-line no-param-reassign
} }
return connect(id, password, roomName).catch(err => { return connect(id, password, roomName).catch(err => {

View File

@ -72,9 +72,9 @@
+ "font-size: medium;" + "font-size: medium;"
+ "font-weight: 400;" + "font-weight: 400;"
+ "transform: translate(-50%, -50%)'>" + "transform: translate(-50%, -50%)'>"
+ "Uh oh! We couldn't fully download everything we needed :(" // jshint ignore:line + "Uh oh! We couldn't fully download everything we needed :("
+ "<br/> " + "<br/> "
+ "We will try again shortly. In the mean time, check for problems with your Internet connection!" // jshint ignore:line + "We will try again shortly. In the mean time, check for problems with your Internet connection!"
+ "<br/><br/> " + "<br/><br/> "
+ "<div id='moreInfo' style='" + "<div id='moreInfo' style='"
+ "display: none;'>" + "Missing " + fileRef + "display: none;'>" + "Missing " + fileRef

View File

@ -1,7 +1,9 @@
var interfaceConfig = { // eslint-disable-line no-unused-vars /* eslint-disable no-unused-vars, no-var, max-len */
var interfaceConfig = {
// TO FIX: this needs to be handled from SASS variables. There are some // TO FIX: this needs to be handled from SASS variables. There are some
// methods allowing to use variables both in css and js. // methods allowing to use variables both in css and js.
DEFAULT_BACKGROUND: '#474747', DEFAULT_BACKGROUND: '#474747',
/** /**
* In case the desktop sharing is disabled through the config the button * In case the desktop sharing is disabled through the config the button
* will not be hidden, but displayed as disabled with this text us as * will not be hidden, but displayed as disabled with this text us as
@ -10,45 +12,53 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP: null, DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP: null,
INITIAL_TOOLBAR_TIMEOUT: 20000, INITIAL_TOOLBAR_TIMEOUT: 20000,
TOOLBAR_TIMEOUT: 4000, TOOLBAR_TIMEOUT: 4000,
DEFAULT_REMOTE_DISPLAY_NAME: "Fellow Jitster", DEFAULT_REMOTE_DISPLAY_NAME: 'Fellow Jitster',
DEFAULT_LOCAL_DISPLAY_NAME: "me", DEFAULT_LOCAL_DISPLAY_NAME: 'me',
SHOW_JITSI_WATERMARK: true, SHOW_JITSI_WATERMARK: true,
JITSI_WATERMARK_LINK: "https://jitsi.org", JITSI_WATERMARK_LINK: 'https://jitsi.org',
// if watermark is disabled by default, it can be shown only for guests // if watermark is disabled by default, it can be shown only for guests
SHOW_WATERMARK_FOR_GUESTS: true, SHOW_WATERMARK_FOR_GUESTS: true,
SHOW_BRAND_WATERMARK: false, SHOW_BRAND_WATERMARK: false,
BRAND_WATERMARK_LINK: "", BRAND_WATERMARK_LINK: '',
SHOW_POWERED_BY: false, SHOW_POWERED_BY: false,
GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true, GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,
APP_NAME: "Jitsi Meet", APP_NAME: 'Jitsi Meet',
LANG_DETECTION: false, // Allow i18n to detect the system language LANG_DETECTION: false, // Allow i18n to detect the system language
INVITATION_POWERED_BY: true, INVITATION_POWERED_BY: true,
/** /**
* If we should show authentication block in profile * If we should show authentication block in profile
*/ */
AUTHENTICATION_ENABLE: true, AUTHENTICATION_ENABLE: true,
/** /**
* the toolbar buttons line is intentionally left in one line, to be able * the toolbar buttons line is intentionally left in one line, to be able
* to easily override values or remove them using regex * to easily override values or remove them using regex
*/ */
TOOLBAR_BUTTONS: [ TOOLBAR_BUTTONS: [
//main toolbar
'microphone', 'camera', 'desktop', 'invite', 'fullscreen', 'fodeviceselection', 'hangup', // jshint ignore:line // main toolbar
//extended toolbar 'microphone', 'camera', 'desktop', 'invite', 'fullscreen', 'fodeviceselection', 'hangup',
'profile', 'contacts', 'info', 'chat', 'recording', 'etherpad', 'sharedvideo', 'settings', 'raisehand', 'videoquality', 'filmstrip'], // jshint ignore:line
// extended toolbar
'profile', 'contacts', 'info', 'chat', 'recording', 'etherpad', 'sharedvideo', 'settings', 'raisehand', 'videoquality', 'filmstrip' ],
/** /**
* Main Toolbar Buttons * Main Toolbar Buttons
* All of them should be in TOOLBAR_BUTTONS * All of them should be in TOOLBAR_BUTTONS
*/ */
MAIN_TOOLBAR_BUTTONS: ['microphone', 'camera', 'desktop', 'invite', 'fullscreen', 'fodeviceselection', 'hangup'], // jshint ignore:line MAIN_TOOLBAR_BUTTONS: [ 'microphone', 'camera', 'desktop', 'invite', 'fullscreen', 'fodeviceselection', 'hangup' ],
SETTINGS_SECTIONS: ['language', 'devices', 'moderator'], SETTINGS_SECTIONS: [ 'language', 'devices', 'moderator' ],
INVITE_OPTIONS: ['invite', 'dialout', 'addtocall'], INVITE_OPTIONS: [ 'invite', 'dialout', 'addtocall' ],
// Determines how the video would fit the screen. 'both' would fit the whole // 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, '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, 'width' would fit the original video width to the width of the
// screen respecting ratio. // screen respecting ratio.
VIDEO_LAYOUT_FIT: 'both', VIDEO_LAYOUT_FIT: 'both',
SHOW_CONTACTLIST_AVATARS: true, SHOW_CONTACTLIST_AVATARS: true,
/** /**
* Whether to only show the filmstrip (and hide the toolbar). * Whether to only show the filmstrip (and hide the toolbar).
*/ */
@ -59,11 +69,12 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
*/ */
VERTICAL_FILMSTRIP: true, VERTICAL_FILMSTRIP: true,
//A html text to be shown to guests on the close page, false disables it // A html text to be shown to guests on the close page, false disables it
CLOSE_PAGE_GUEST_HINT: false, CLOSE_PAGE_GUEST_HINT: false,
RANDOM_AVATAR_URL_PREFIX: false, RANDOM_AVATAR_URL_PREFIX: false,
RANDOM_AVATAR_URL_SUFFIX: false, RANDOM_AVATAR_URL_SUFFIX: false,
FILM_STRIP_MAX_HEIGHT: 120, FILM_STRIP_MAX_HEIGHT: 120,
// Enables feedback star animation. // Enables feedback star animation.
ENABLE_FEEDBACK_ANIMATION: false, ENABLE_FEEDBACK_ANIMATION: false,
DISABLE_FOCUS_INDICATOR: false, DISABLE_FOCUS_INDICATOR: false,
@ -76,13 +87,13 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
* @type {boolean} * @type {boolean}
*/ */
DISABLE_RINGING: false, DISABLE_RINGING: false,
AUDIO_LEVEL_PRIMARY_COLOR: "rgba(255,255,255,0.4)", AUDIO_LEVEL_PRIMARY_COLOR: 'rgba(255,255,255,0.4)',
AUDIO_LEVEL_SECONDARY_COLOR: "rgba(255,255,255,0.2)", AUDIO_LEVEL_SECONDARY_COLOR: 'rgba(255,255,255,0.2)',
POLICY_LOGO: null, POLICY_LOGO: null,
LOCAL_THUMBNAIL_RATIO: 16/9, //16:9 LOCAL_THUMBNAIL_RATIO: 16 / 9, // 16:9
REMOTE_THUMBNAIL_RATIO: 1, //1:1 REMOTE_THUMBNAIL_RATIO: 1, // 1:1
// Documentation reference for the live streaming feature. // Documentation reference for the live streaming feature.
LIVE_STREAMING_HELP_LINK: "https://jitsi.org/live", LIVE_STREAMING_HELP_LINK: 'https://jitsi.org/live',
/** /**
* Whether the mobile app Jitsi Meet is to be promoted to participants * Whether the mobile app Jitsi Meet is to be promoted to participants
@ -131,3 +142,4 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
*/ */
// ADD_PEOPLE_APP_NAME: "" // ADD_PEOPLE_APP_NAME: ""
}; };
/* eslint-enable no-unused-vars, no-var, max-len */

View File

@ -1,10 +1,15 @@
/* eslint-disable no-unused-vars, no-var */
// Logging configuration // Logging configuration
var loggingConfig = { // eslint-disable-line no-unused-vars var loggingConfig = {
//default log level for the app and lib-jitsi-meet // default log level for the app and lib-jitsi-meet
defaultLogLevel: 'trace', defaultLogLevel: 'trace',
// Option to disable LogCollector (which stores the logs on CallStats) // Option to disable LogCollector (which stores the logs on CallStats)
//disableLogCollector: true, // disableLogCollector: true,
// Logging level adjustments for verbose modules: // Logging level adjustments for verbose modules:
'modules/xmpp/strophe.util.js': 'log', 'modules/xmpp/strophe.util.js': 'log',
'modules/statistics/CallStats.js': 'info' 'modules/statistics/CallStats.js': 'info'
}; };
/* eslint-enable no-unused-vars, no-var */

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
const logger = require("jitsi-meet-logger").getLogger(__filename); const logger = require('jitsi-meet-logger').getLogger(__filename);
import UIEvents from '../service/UI/UIEvents'; import UIEvents from '../service/UI/UIEvents';
import VideoLayout from './UI/videolayout/VideoLayout'; import VideoLayout from './UI/videolayout/VideoLayout';
@ -23,7 +23,7 @@ import VideoLayout from './UI/videolayout/VideoLayout';
* {State} for the local state at the time of this writing) of a {FollowMe} * {State} for the local state at the time of this writing) of a {FollowMe}
* (instance) between participants. * (instance) between participants.
*/ */
const _COMMAND = "follow-me"; const _COMMAND = 'follow-me';
/** /**
* The timeout after which a follow-me command that has been received will be * The timeout after which a follow-me command that has been received will be
@ -50,34 +50,61 @@ class State {
* the property, the old value of the property before the change, and the * the property, the old value of the property before the change, and the
* new value of the property after the change. * new value of the property after the change.
*/ */
constructor (propertyChangeCallback) { constructor(propertyChangeCallback) {
this._propertyChangeCallback = propertyChangeCallback; this._propertyChangeCallback = propertyChangeCallback;
} }
get filmstripVisible () { return this._filmstripVisible; } /**
*
*/
get filmstripVisible() {
return this._filmstripVisible;
}
/**
*
*/
set filmstripVisible(b) {
const oldValue = this._filmstripVisible;
set filmstripVisible (b) {
var oldValue = this._filmstripVisible;
if (oldValue !== b) { if (oldValue !== b) {
this._filmstripVisible = b; this._filmstripVisible = b;
this._firePropertyChange('filmstripVisible', oldValue, b); this._firePropertyChange('filmstripVisible', oldValue, b);
} }
} }
get nextOnStage() { return this._nextOnStage; } /**
*
*/
get nextOnStage() {
return this._nextOnStage;
}
/**
*
*/
set nextOnStage(id) { set nextOnStage(id) {
var oldValue = this._nextOnStage; const oldValue = this._nextOnStage;
if (oldValue !== id) { if (oldValue !== id) {
this._nextOnStage = id; this._nextOnStage = id;
this._firePropertyChange('nextOnStage', oldValue, id); this._firePropertyChange('nextOnStage', oldValue, id);
} }
} }
get sharedDocumentVisible () { return this._sharedDocumentVisible; } /**
*
*/
get sharedDocumentVisible() {
return this._sharedDocumentVisible;
}
/**
*
*/
set sharedDocumentVisible(b) {
const oldValue = this._sharedDocumentVisible;
set sharedDocumentVisible (b) {
var oldValue = this._sharedDocumentVisible;
if (oldValue !== b) { if (oldValue !== b) {
this._sharedDocumentVisible = b; this._sharedDocumentVisible = b;
this._firePropertyChange('sharedDocumentVisible', oldValue, b); this._firePropertyChange('sharedDocumentVisible', oldValue, b);
@ -93,10 +120,12 @@ class State {
* @param oldValue the value of {property} before the change * @param oldValue the value of {property} before the change
* @param newValue the value of {property} after the change * @param newValue the value of {property} after the change
*/ */
_firePropertyChange (property, oldValue, newValue) { _firePropertyChange(property, oldValue, newValue) {
var propertyChangeCallback = this._propertyChangeCallback; const propertyChangeCallback = this._propertyChangeCallback;
if (propertyChangeCallback)
if (propertyChangeCallback) {
propertyChangeCallback(property, oldValue, newValue); propertyChangeCallback(property, oldValue, newValue);
}
} }
} }
@ -118,7 +147,7 @@ class FollowMe {
* destination (model/state) to receive from the remote moderator if the * destination (model/state) to receive from the remote moderator if the
* local participant is not the moderator * local participant is not the moderator
*/ */
constructor (conference, UI) { constructor(conference, UI) {
this._conference = conference; this._conference = conference;
this._UI = UI; this._UI = UI;
this.nextOnStageTimer = 0; this.nextOnStageTimer = 0;
@ -150,9 +179,10 @@ class FollowMe {
this._nextOnStage(pinnedId, Boolean(pinnedId)); this._nextOnStage(pinnedId, Boolean(pinnedId));
// check whether shared document is enabled/initialized // check whether shared document is enabled/initialized
if(this._UI.getSharedDocumentManager()) if (this._UI.getSharedDocumentManager()) {
this._sharedDocumentToggled this._sharedDocumentToggled
.bind(this, this._UI.getSharedDocumentManager().isVisible()); .bind(this, this._UI.getSharedDocumentManager().isVisible());
}
} }
/** /**
@ -162,20 +192,21 @@ class FollowMe {
* the states of interest. * the states of interest.
* @private * @private
*/ */
_addFollowMeListeners () { _addFollowMeListeners() {
this.filmstripEventHandler = this._filmstripToggled.bind(this); this.filmstripEventHandler = this._filmstripToggled.bind(this);
this._UI.addListener(UIEvents.TOGGLED_FILMSTRIP, this._UI.addListener(UIEvents.TOGGLED_FILMSTRIP,
this.filmstripEventHandler); this.filmstripEventHandler);
var self = this; const self = this;
this.pinnedEndpointEventHandler = function (videoId, isPinned) {
this.pinnedEndpointEventHandler = function(videoId, isPinned) {
self._nextOnStage(videoId, isPinned); self._nextOnStage(videoId, isPinned);
}; };
this._UI.addListener(UIEvents.PINNED_ENDPOINT, this._UI.addListener(UIEvents.PINNED_ENDPOINT,
this.pinnedEndpointEventHandler); this.pinnedEndpointEventHandler);
this.sharedDocEventHandler = this._sharedDocumentToggled.bind(this); this.sharedDocEventHandler = this._sharedDocumentToggled.bind(this);
this._UI.addListener( UIEvents.TOGGLED_SHARED_DOCUMENT, this._UI.addListener(UIEvents.TOGGLED_SHARED_DOCUMENT,
this.sharedDocEventHandler); this.sharedDocEventHandler);
} }
@ -183,7 +214,7 @@ class FollowMe {
* Removes all follow me listeners. * Removes all follow me listeners.
* @private * @private
*/ */
_removeFollowMeListeners () { _removeFollowMeListeners() {
this._UI.removeListener(UIEvents.TOGGLED_FILMSTRIP, this._UI.removeListener(UIEvents.TOGGLED_FILMSTRIP,
this.filmstripEventHandler); this.filmstripEventHandler);
this._UI.removeListener(UIEvents.TOGGLED_SHARED_DOCUMENT, this._UI.removeListener(UIEvents.TOGGLED_SHARED_DOCUMENT,
@ -198,13 +229,13 @@ class FollowMe {
* @param enable {true} to enable the follow me functionality, {false} - * @param enable {true} to enable the follow me functionality, {false} -
* to disable it * to disable it
*/ */
enableFollowMe (enable) { enableFollowMe(enable) {
if (enable) { if (enable) {
this._setFollowMeInitialState(); this._setFollowMeInitialState();
this._addFollowMeListeners(); this._addFollowMeListeners();
} } else {
else
this._removeFollowMeListeners(); this._removeFollowMeListeners();
}
} }
/** /**
@ -214,7 +245,7 @@ class FollowMe {
* @param filmstripVisible {Boolean} {true} if the filmstrip was shown (as a * @param filmstripVisible {Boolean} {true} if the filmstrip was shown (as a
* result of the toggle) or {false} if the filmstrip was hidden * result of the toggle) or {false} if the filmstrip was hidden
*/ */
_filmstripToggled (filmstripVisible) { _filmstripToggled(filmstripVisible) {
this._local.filmstripVisible = filmstripVisible; this._local.filmstripVisible = filmstripVisible;
} }
@ -225,7 +256,7 @@ class FollowMe {
* @param sharedDocumentVisible {Boolean} {true} if the shared document was * @param sharedDocumentVisible {Boolean} {true} if the shared document was
* shown (as a result of the toggle) or {false} if it was hidden * shown (as a result of the toggle) or {false} if it was hidden
*/ */
_sharedDocumentToggled (sharedDocumentVisible) { _sharedDocumentToggled(sharedDocumentVisible) {
this._local.sharedDocumentVisible = sharedDocumentVisible; this._local.sharedDocumentVisible = sharedDocumentVisible;
} }
@ -237,13 +268,16 @@ class FollowMe {
* unpinned * unpinned
* @private * @private
*/ */
_nextOnStage (videoId, isPinned) { _nextOnStage(videoId, isPinned) {
if (!this._conference.isModerator) if (!this._conference.isModerator) {
return; return;
}
var nextOnStage = null; let nextOnStage = null;
if(isPinned)
if (isPinned) {
nextOnStage = videoId; nextOnStage = videoId;
}
this._local.nextOnStage = nextOnStage; this._local.nextOnStage = nextOnStage;
} }
@ -251,24 +285,25 @@ class FollowMe {
/** /**
* Sends the follow-me command, when a local property change occurs. * Sends the follow-me command, when a local property change occurs.
* *
* @param property the property name
* @param oldValue the old value
* @param newValue the new value
* @private * @private
*/ */
// eslint-disable-next-line no-unused-vars _localPropertyChange() { // eslint-disable-next-line no-unused-vars
_localPropertyChange (property, oldValue, newValue) {
// Only a moderator is allowed to send commands. // Only a moderator is allowed to send commands.
const conference = this._conference; const conference = this._conference;
if (!conference.isModerator)
if (!conference.isModerator) {
return; return;
}
const commands = conference.commands; const commands = conference.commands;
// XXX The "Follow Me" command represents a snapshot of all states // XXX The "Follow Me" command represents a snapshot of all states
// which are to be followed so don't forget to removeCommand before // which are to be followed so don't forget to removeCommand before
// sendCommand! // sendCommand!
commands.removeCommand(_COMMAND); commands.removeCommand(_COMMAND);
const local = this._local; const local = this._local;
commands.sendCommandOnce( commands.sendCommandOnce(
_COMMAND, _COMMAND,
{ {
@ -289,22 +324,24 @@ class FollowMe {
* notable idiosyncrasy of the Command(s) API to be mindful of here is that * notable idiosyncrasy of the Command(s) API to be mindful of here is that
* the command may be issued by the local participant. * the command may be issued by the local participant.
*/ */
_onFollowMeCommand ({ attributes }, id) { _onFollowMeCommand({ attributes }, id) {
// We require to know who issued the command because (1) only a // We require to know who issued the command because (1) only a
// moderator is allowed to send commands and (2) a command MUST be // moderator is allowed to send commands and (2) a command MUST be
// issued by a defined commander. // issued by a defined commander.
if (typeof id === 'undefined') if (typeof id === 'undefined') {
return; return;
}
// The Command(s) API will send us our own commands and we don't want // The Command(s) API will send us our own commands and we don't want
// to act upon them. // to act upon them.
if (this._conference.isLocalId(id)) if (this._conference.isLocalId(id)) {
return; return;
}
if (!this._conference.isParticipantModerator(id)) {
logger.warn('Received follow-me command '
+ 'not from moderator');
if (!this._conference.isParticipantModerator(id))
{
logger.warn('Received follow-me command ' +
'not from moderator');
return; return;
} }
@ -328,14 +365,16 @@ class FollowMe {
// attributes, at least) at the time of this writing so take into // attributes, at least) at the time of this writing so take into
// account that what originated as a Boolean may be a String on // account that what originated as a Boolean may be a String on
// receipt. // receipt.
filmstripVisible = (filmstripVisible == 'true'); // eslint-disable-next-line eqeqeq, no-param-reassign
filmstripVisible = filmstripVisible == 'true';
// FIXME The UI (module) very likely doesn't (want to) expose its // FIXME The UI (module) very likely doesn't (want to) expose its
// eventEmitter as a public field. I'm not sure at the time of this // eventEmitter as a public field. I'm not sure at the time of this
// writing whether calling UI.toggleFilmstrip() is acceptable (from // writing whether calling UI.toggleFilmstrip() is acceptable (from
// a design standpoint) either. // a design standpoint) either.
if (filmstripVisible !== this._UI.isFilmstripVisible()) if (filmstripVisible !== this._UI.isFilmstripVisible()) {
this._UI.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP); this._UI.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP);
}
} }
} }
@ -347,22 +386,24 @@ class FollowMe {
* @private * @private
*/ */
_onNextOnStage(id) { _onNextOnStage(id) {
var clickId = null; let clickId = null;
var pin; let pin;
// if there is an id which is not pinned we schedule it for pin only the // if there is an id which is not pinned we schedule it for pin only the
// first time // first time
if(typeof id !== 'undefined' && !VideoLayout.isPinned(id)) {
if (typeof id !== 'undefined' && !VideoLayout.isPinned(id)) {
clickId = id; clickId = id;
pin = true; pin = true;
} } else if (typeof id === 'undefined' && VideoLayout.getPinnedId()) {
// if there is no id, but we have a pinned one, let's unpin // if there is no id, but we have a pinned one, let's unpin
else if (typeof id == 'undefined' && VideoLayout.getPinnedId()) {
clickId = VideoLayout.getPinnedId(); clickId = VideoLayout.getPinnedId();
pin = false; pin = false;
} }
if (clickId) if (clickId) {
this._pinVideoThumbnailById(clickId, pin); this._pinVideoThumbnailById(clickId, pin);
}
} }
/** /**
@ -378,11 +419,13 @@ class FollowMe {
// attributes, at least) at the time of this writing so take into // attributes, at least) at the time of this writing so take into
// account that what originated as a Boolean may be a String on // account that what originated as a Boolean may be a String on
// receipt. // receipt.
sharedDocumentVisible = (sharedDocumentVisible == 'true'); // eslint-disable-next-line eqeqeq, no-param-reassign
sharedDocumentVisible = sharedDocumentVisible == 'true';
if (sharedDocumentVisible if (sharedDocumentVisible
!== this._UI.getSharedDocumentManager().isVisible()) !== this._UI.getSharedDocumentManager().isVisible()) {
this._UI.getSharedDocumentManager().toggleEtherpad(); this._UI.getSharedDocumentManager().toggleEtherpad();
}
} }
} }
@ -394,27 +437,31 @@ class FollowMe {
* @private * @private
*/ */
_pinVideoThumbnailById(clickId, pin) { _pinVideoThumbnailById(clickId, pin) {
var self = this; const self = this;
var smallVideo = VideoLayout.getSmallVideo(clickId); const smallVideo = VideoLayout.getSmallVideo(clickId);
// If the SmallVideo for the given clickId exists we proceed with the // If the SmallVideo for the given clickId exists we proceed with the
// pin/unpin. // pin/unpin.
if (smallVideo) { if (smallVideo) {
this.nextOnStageTimer = 0; this.nextOnStageTimer = 0;
clearTimeout(this.nextOnStageTimout); clearTimeout(this.nextOnStageTimout);
/* eslint-disable no-mixed-operators */
if (pin && !VideoLayout.isPinned(clickId) if (pin && !VideoLayout.isPinned(clickId)
|| !pin && VideoLayout.isPinned(clickId)) || !pin && VideoLayout.isPinned(clickId)) {
/* eslint-disable no-mixed-operators */
VideoLayout.handleVideoThumbClicked(clickId); VideoLayout.handleVideoThumbClicked(clickId);
} }
// If there's no SmallVideo object for the given id, lets wait and see } else {
// if it's going to be created in the next 30sec. // If there's no SmallVideo object for the given id, lets wait and
else { // see if it's going to be created in the next 30sec.
this.nextOnStageTimout = setTimeout(function () { this.nextOnStageTimout = setTimeout(function() {
if (self.nextOnStageTimer > _FOLLOW_ME_RECEIVED_TIMEOUT) { if (self.nextOnStageTimer > _FOLLOW_ME_RECEIVED_TIMEOUT) {
self.nextOnStageTimer = 0; self.nextOnStageTimer = 0;
return; return;
} }
// eslint-disable-next-line no-invalid-this
this.nextOnStageTimer++; this.nextOnStageTimer++;
self._pinVideoThumbnailById(clickId, pin); self._pinVideoThumbnailById(clickId, pin);
}, 1000); }, 1000);

View File

@ -1,24 +1,24 @@
/* global APP, $, config, interfaceConfig */ /* global APP, $, config, interfaceConfig */
const logger = require("jitsi-meet-logger").getLogger(__filename); const logger = require('jitsi-meet-logger').getLogger(__filename);
var UI = {}; const UI = {};
import Chat from "./side_pannels/chat/Chat"; import Chat from './side_pannels/chat/Chat';
import SidePanels from "./side_pannels/SidePanels"; import SidePanels from './side_pannels/SidePanels';
import Avatar from "./avatar/Avatar"; import Avatar from './avatar/Avatar';
import SideContainerToggler from "./side_pannels/SideContainerToggler"; import SideContainerToggler from './side_pannels/SideContainerToggler';
import messageHandler from "./util/MessageHandler"; import messageHandler from './util/MessageHandler';
import UIUtil from "./util/UIUtil"; import UIUtil from './util/UIUtil';
import UIEvents from "../../service/UI/UIEvents"; import UIEvents from '../../service/UI/UIEvents';
import EtherpadManager from './etherpad/Etherpad'; import EtherpadManager from './etherpad/Etherpad';
import SharedVideoManager from './shared_video/SharedVideo'; import SharedVideoManager from './shared_video/SharedVideo';
import Recording from "./recording/Recording"; import Recording from './recording/Recording';
import VideoLayout from "./videolayout/VideoLayout"; import VideoLayout from './videolayout/VideoLayout';
import Filmstrip from "./videolayout/Filmstrip"; import Filmstrip from './videolayout/Filmstrip';
import SettingsMenu from "./side_pannels/settings/SettingsMenu"; import SettingsMenu from './side_pannels/settings/SettingsMenu';
import Profile from "./side_pannels/profile/Profile"; import Profile from './side_pannels/profile/Profile';
import { import {
openDeviceSelectionDialog openDeviceSelectionDialog
@ -43,11 +43,13 @@ import {
showToolbox showToolbox
} from '../../react/features/toolbox'; } from '../../react/features/toolbox';
var EventEmitter = require("events"); const EventEmitter = require('events');
UI.messageHandler = messageHandler;
import FollowMe from "../FollowMe"; UI.messageHandler = messageHandler;
import FollowMe from '../FollowMe';
const eventEmitter = new EventEmitter();
var eventEmitter = new EventEmitter();
UI.eventEmitter = eventEmitter; UI.eventEmitter = eventEmitter;
let etherpadManager; let etherpadManager;
@ -62,38 +64,82 @@ const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
.camera[JitsiTrackErrors.UNSUPPORTED_RESOLUTION] .camera[JitsiTrackErrors.UNSUPPORTED_RESOLUTION]
= "dialog.cameraUnsupportedResolutionError"; = 'dialog.cameraUnsupportedResolutionError';
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[JitsiTrackErrors.GENERAL] JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[JitsiTrackErrors.GENERAL]
= "dialog.cameraUnknownError"; = 'dialog.cameraUnknownError';
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[JitsiTrackErrors.PERMISSION_DENIED] JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[JitsiTrackErrors.PERMISSION_DENIED]
= "dialog.cameraPermissionDeniedError"; = 'dialog.cameraPermissionDeniedError';
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[JitsiTrackErrors.NOT_FOUND] JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[JitsiTrackErrors.NOT_FOUND]
= "dialog.cameraNotFoundError"; = 'dialog.cameraNotFoundError';
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[JitsiTrackErrors.CONSTRAINT_FAILED] JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[JitsiTrackErrors.CONSTRAINT_FAILED]
= "dialog.cameraConstraintFailedError"; = 'dialog.cameraConstraintFailedError';
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
.camera[JitsiTrackErrors.NO_DATA_FROM_SOURCE] .camera[JitsiTrackErrors.NO_DATA_FROM_SOURCE]
= "dialog.cameraNotSendingData"; = 'dialog.cameraNotSendingData';
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[JitsiTrackErrors.GENERAL] JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[JitsiTrackErrors.GENERAL]
= "dialog.micUnknownError"; = 'dialog.micUnknownError';
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
.microphone[JitsiTrackErrors.PERMISSION_DENIED] .microphone[JitsiTrackErrors.PERMISSION_DENIED]
= "dialog.micPermissionDeniedError"; = 'dialog.micPermissionDeniedError';
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[JitsiTrackErrors.NOT_FOUND] JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[JitsiTrackErrors.NOT_FOUND]
= "dialog.micNotFoundError"; = 'dialog.micNotFoundError';
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
.microphone[JitsiTrackErrors.CONSTRAINT_FAILED] .microphone[JitsiTrackErrors.CONSTRAINT_FAILED]
= "dialog.micConstraintFailedError"; = 'dialog.micConstraintFailedError';
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
.microphone[JitsiTrackErrors.NO_DATA_FROM_SOURCE] .microphone[JitsiTrackErrors.NO_DATA_FROM_SOURCE]
= "dialog.micNotSendingData"; = 'dialog.micNotSendingData';
const UIListeners = new Map([
[
UIEvents.ETHERPAD_CLICKED,
() => etherpadManager && etherpadManager.toggleEtherpad()
], [
UIEvents.SHARED_VIDEO_CLICKED,
() => sharedVideoManager && sharedVideoManager.toggleSharedVideo()
], [
UIEvents.TOGGLE_FULLSCREEN,
() => UI.toggleFullScreen()
], [
UIEvents.TOGGLE_CHAT,
() => UI.toggleChat()
], [
UIEvents.TOGGLE_SETTINGS,
() => {
// Opening of device selection is special-cased as it is a dialog
// opened through a button in settings and not directly displayed in
// settings itself. As it is not useful to only have a settings menu
// with a button to open a dialog, open the dialog directly instead.
if (interfaceConfig.SETTINGS_SECTIONS.length === 1
&& UIUtil.isSettingEnabled('devices')) {
APP.store.dispatch(openDeviceSelectionDialog());
} else {
UI.toggleSidePanel('settings_container');
}
}
], [
UIEvents.TOGGLE_CONTACT_LIST,
() => UI.toggleContactList()
], [
UIEvents.TOGGLE_PROFILE,
() => {
UI.toggleSidePanel('profile_container');
}
], [
UIEvents.TOGGLE_FILMSTRIP,
() => UI.handleToggleFilmstrip()
], [
UIEvents.FOLLOW_ME_ENABLED,
enabled => followMeHandler && followMeHandler.enableFollowMe(enabled)
]
]);
/** /**
* Toggles the application in and out of full screen mode * Toggles the application in and out of full screen mode
* (a.k.a. presentation mode in Chrome). * (a.k.a. presentation mode in Chrome).
*/ */
UI.toggleFullScreen = function() { UI.toggleFullScreen = function() {
(UIUtil.isFullScreen()) UIUtil.isFullScreen()
? UIUtil.exitFullScreen() ? UIUtil.exitFullScreen()
: UIUtil.enterFullScreen(); : UIUtil.enterFullScreen();
}; };
@ -101,7 +147,7 @@ UI.toggleFullScreen = function() {
/** /**
* Notify user that server has shut down. * Notify user that server has shut down.
*/ */
UI.notifyGracefulShutdown = function () { UI.notifyGracefulShutdown = function() {
messageHandler.openMessageDialog( messageHandler.openMessageDialog(
'dialog.serviceUnavailable', 'dialog.serviceUnavailable',
'dialog.gracefulShutdown' 'dialog.gracefulShutdown'
@ -111,31 +157,33 @@ UI.notifyGracefulShutdown = function () {
/** /**
* Notify user that reservation error happened. * Notify user that reservation error happened.
*/ */
UI.notifyReservationError = function (code, msg) { UI.notifyReservationError = function(code, msg) {
var message = APP.translation.generateTranslationHTML( const message = APP.translation.generateTranslationHTML(
"dialog.reservationErrorMsg", {code: code, msg: msg}); 'dialog.reservationErrorMsg', { code,
msg });
messageHandler.openDialog( messageHandler.openDialog(
"dialog.reservationError", message, true, {}, () => false); 'dialog.reservationError', message, true, {}, () => false);
}; };
/** /**
* Notify user that he has been kicked from the server. * Notify user that he has been kicked from the server.
*/ */
UI.notifyKicked = function () { UI.notifyKicked = function() {
messageHandler.openMessageDialog( messageHandler.openMessageDialog(
"dialog.sessTerminated", 'dialog.sessTerminated',
"dialog.kickMessage"); 'dialog.kickMessage');
}; };
/** /**
* Notify user that conference was destroyed. * Notify user that conference was destroyed.
* @param reason {string} the reason text * @param reason {string} the reason text
*/ */
UI.notifyConferenceDestroyed = function (reason) { UI.notifyConferenceDestroyed = function(reason) {
//FIXME: use Session Terminated from translation, but // FIXME: use Session Terminated from translation, but
// 'reason' text comes from XMPP packet and is not translated // 'reason' text comes from XMPP packet and is not translated
messageHandler.openDialog( messageHandler.openDialog(
"dialog.sessTerminated", reason, true, {}, () => false); 'dialog.sessTerminated', reason, true, {}, () => false);
}; };
/** /**
@ -143,7 +191,7 @@ UI.notifyConferenceDestroyed = function (reason) {
* @param err the Error * @param err the Error
* @param msg * @param msg
*/ */
UI.showChatError = function (err, msg) { UI.showChatError = function(err, msg) {
if (interfaceConfig.filmStripOnly) { if (interfaceConfig.filmStripOnly) {
return; return;
} }
@ -155,12 +203,12 @@ UI.showChatError = function (err, msg) {
* @param {string} id user id * @param {string} id user id
* @param {string} displayName new nickname * @param {string} displayName new nickname
*/ */
UI.changeDisplayName = function (id, displayName) { UI.changeDisplayName = function(id, displayName) {
VideoLayout.onDisplayNameChanged(id, displayName); VideoLayout.onDisplayNameChanged(id, displayName);
if (APP.conference.isLocalId(id) || id === 'localVideoContainer') { if (APP.conference.isLocalId(id) || id === 'localVideoContainer') {
Profile.changeDisplayName(displayName); Profile.changeDisplayName(displayName);
Chat.setChatConversationMode(!!displayName); Chat.setChatConversationMode(Boolean(displayName));
} }
}; };
@ -171,7 +219,7 @@ UI.changeDisplayName = function (id, displayName) {
* currently in the interrupted state or <tt>false</tt> if the connection * currently in the interrupted state or <tt>false</tt> if the connection
* is fine. * is fine.
*/ */
UI.showLocalConnectionInterrupted = function (isInterrupted) { UI.showLocalConnectionInterrupted = function(isInterrupted) {
VideoLayout.showLocalConnectionInterrupted(isInterrupted); VideoLayout.showLocalConnectionInterrupted(isInterrupted);
}; };
@ -198,7 +246,7 @@ UI.setLocalRaisedHandStatus
/** /**
* Initialize conference UI. * Initialize conference UI.
*/ */
UI.initConference = function () { UI.initConference = function() {
const { dispatch, getState } = APP.store; const { dispatch, getState } = APP.store;
const { avatarID, email, id, name } = getLocalParticipant(getState); const { avatarID, email, id, name } = getLocalParticipant(getState);
@ -208,7 +256,7 @@ UI.initConference = function () {
UI.showToolbar(); UI.showToolbar();
let displayName = config.displayJids ? id : name; const displayName = config.displayJids ? id : name;
if (displayName) { if (displayName) {
UI.changeDisplayName('localVideoContainer', displayName); UI.changeDisplayName('localVideoContainer', displayName);
@ -230,7 +278,7 @@ UI.initConference = function () {
followMeHandler = new FollowMe(APP.conference, UI); followMeHandler = new FollowMe(APP.conference, UI);
}; };
UI.mucJoined = function () { UI.mucJoined = function() {
VideoLayout.mucJoined(); VideoLayout.mucJoined();
// Update local video now that a conference is joined a user ID should be // Update local video now that a conference is joined a user ID should be
@ -240,7 +288,7 @@ UI.mucJoined = function () {
APP.conference.getLocalDisplayName()); APP.conference.getLocalDisplayName());
}; };
/*** /** *
* Handler for toggling filmstrip * Handler for toggling filmstrip
*/ */
UI.handleToggleFilmstrip = () => UI.toggleFilmstrip(); UI.handleToggleFilmstrip = () => UI.toggleFilmstrip();
@ -249,7 +297,7 @@ UI.handleToggleFilmstrip = () => UI.toggleFilmstrip();
* Returns the shared document manager object. * Returns the shared document manager object.
* @return {EtherpadManager} the shared document manager object * @return {EtherpadManager} the shared document manager object
*/ */
UI.getSharedVideoManager = function () { UI.getSharedVideoManager = function() {
return sharedVideoManager; return sharedVideoManager;
}; };
@ -259,11 +307,11 @@ UI.getSharedVideoManager = function () {
* @returns {boolean} true if the UI is ready and the conference should be * @returns {boolean} true if the UI is ready and the conference should be
* established, false - otherwise (for example in the case of welcome page) * established, false - otherwise (for example in the case of welcome page)
*/ */
UI.start = function () { UI.start = function() {
document.title = interfaceConfig.APP_NAME; document.title = interfaceConfig.APP_NAME;
// Set the defaults for prompt dialogs. // Set the defaults for prompt dialogs.
$.prompt.setDefaults({persistent: false}); $.prompt.setDefaults({ persistent: false });
SideContainerToggler.init(eventEmitter); SideContainerToggler.init(eventEmitter);
@ -276,22 +324,24 @@ UI.start = function () {
VideoLayout.resizeVideoArea(true, true); VideoLayout.resizeVideoArea(true, true);
sharedVideoManager = new SharedVideoManager(eventEmitter); sharedVideoManager = new SharedVideoManager(eventEmitter);
// eslint-disable-next-line no-negated-condition
if (!interfaceConfig.filmStripOnly) { if (!interfaceConfig.filmStripOnly) {
// Initialise the recording module. // Initialise the recording module.
if (config.enableRecording) { if (config.enableRecording) {
Recording.init(eventEmitter, config.recordingType); Recording.init(eventEmitter, config.recordingType);
} }
// Initialize side panels // Initialize side panels
SidePanels.init(eventEmitter); SidePanels.init(eventEmitter);
} else { } else {
$("body").addClass("filmstrip-only"); $('body').addClass('filmstrip-only');
UI.showToolbar(); UI.showToolbar();
Filmstrip.setFilmstripOnly(); Filmstrip.setFilmstripOnly();
APP.store.dispatch(setNotificationsEnabled(false)); APP.store.dispatch(setNotificationsEnabled(false));
} }
if (interfaceConfig.VERTICAL_FILMSTRIP) { if (interfaceConfig.VERTICAL_FILMSTRIP) {
$("body").addClass("vertical-filmstrip"); $('body').addClass('vertical-filmstrip');
} }
document.title = interfaceConfig.APP_NAME; document.title = interfaceConfig.APP_NAME;
@ -322,6 +372,9 @@ UI.unregisterListeners
* Setup some DOM event listeners. * Setup some DOM event listeners.
*/ */
UI.bindEvents = () => { UI.bindEvents = () => {
/**
*
*/
function onResize() { function onResize() {
SideContainerToggler.resize(); SideContainerToggler.resize();
VideoLayout.resizeVideoArea(); VideoLayout.resizeVideoArea();
@ -364,7 +417,7 @@ UI.addLocalStream = track => {
VideoLayout.changeLocalVideo(track); VideoLayout.changeLocalVideo(track);
break; break;
default: default:
logger.error("Unknown stream type: " + track.getType()); logger.error(`Unknown stream type: ${track.getType()}`);
break; break;
} }
}; };
@ -413,17 +466,18 @@ UI.getSharedDocumentManager = () => etherpadManager;
* Show user on UI. * Show user on UI.
* @param {JitsiParticipant} user * @param {JitsiParticipant} user
*/ */
UI.addUser = function (user) { UI.addUser = function(user) {
var id = user.getId(); const id = user.getId();
var displayName = user.getDisplayName(); const displayName = user.getDisplayName();
messageHandler.participantNotification( messageHandler.participantNotification(
displayName,'notify.somebody', 'connected', 'notify.connected' displayName, 'notify.somebody', 'connected', 'notify.connected'
); );
if (!config.startAudioMuted || if (!config.startAudioMuted
config.startAudioMuted > APP.conference.membersCount) || config.startAudioMuted > APP.conference.membersCount) {
UIUtil.playSoundNotification('userJoined'); UIUtil.playSoundNotification('userJoined');
}
// Add Peer's container // Add Peer's container
VideoLayout.addParticipantContainer(user); VideoLayout.addParticipantContainer(user);
@ -432,8 +486,9 @@ UI.addUser = function (user) {
UI.setUserEmail(id); UI.setUserEmail(id);
// set initial display name // set initial display name
if(displayName) if (displayName) {
UI.changeDisplayName(id, displayName); UI.changeDisplayName(id, displayName);
}
}; };
/** /**
@ -441,9 +496,9 @@ UI.addUser = function (user) {
* @param {string} id user id * @param {string} id user id
* @param {string} displayName user nickname * @param {string} displayName user nickname
*/ */
UI.removeUser = function (id, displayName) { UI.removeUser = function(id, displayName) {
messageHandler.participantNotification( messageHandler.participantNotification(
displayName,'notify.somebody', 'disconnected', 'notify.disconnected' displayName, 'notify.somebody', 'disconnected', 'notify.disconnected'
); );
if (!config.startAudioMuted if (!config.startAudioMuted
@ -476,9 +531,10 @@ UI.updateLocalRole = isModerator => {
SettingsMenu.showFollowMeOptions(isModerator); SettingsMenu.showFollowMeOptions(isModerator);
if (isModerator) { if (isModerator) {
if (!interfaceConfig.DISABLE_FOCUS_INDICATOR) if (!interfaceConfig.DISABLE_FOCUS_INDICATOR) {
messageHandler.participantNotification( messageHandler.participantNotification(
null, "notify.me", 'connected', "notify.moderator"); null, 'notify.me', 'connected', 'notify.moderator');
}
Recording.checkAutoRecord(); Recording.checkAutoRecord();
} }
@ -498,7 +554,8 @@ UI.updateUserRole = user => {
return; return;
} }
var displayName = user.getDisplayName(); const displayName = user.getDisplayName();
if (displayName) { if (displayName) {
messageHandler.participantNotification( messageHandler.participantNotification(
displayName, 'notify.somebody', displayName, 'notify.somebody',
@ -524,9 +581,10 @@ UI.updateUserStatus = (user, status) => {
return; return;
} }
let displayName = user.getDisplayName(); const displayName = user.getDisplayName();
messageHandler.participantNotification( messageHandler.participantNotification(
displayName, '', 'connected', "dialOut.statusMessage", displayName, '', 'connected', 'dialOut.statusMessage',
{ {
status: UIUtil.escapeHtml(status) status: UIUtil.escapeHtml(status)
}); });
@ -540,9 +598,9 @@ UI.toggleSmileys = () => Chat.toggleSmileys();
/** /**
* Toggles filmstrip. * Toggles filmstrip.
*/ */
UI.toggleFilmstrip = function () { UI.toggleFilmstrip = function() {
var self = Filmstrip; // eslint-disable-next-line prefer-rest-params
self.toggleFilmstrip.apply(self, arguments); Filmstrip.toggleFilmstrip(...arguments);
VideoLayout.resizeVideoArea(true, false); VideoLayout.resizeVideoArea(true, false);
}; };
@ -555,12 +613,12 @@ UI.isFilmstripVisible = () => Filmstrip.isFilmstripVisible();
/** /**
* Toggles chat panel. * Toggles chat panel.
*/ */
UI.toggleChat = () => UI.toggleSidePanel("chat_container"); UI.toggleChat = () => UI.toggleSidePanel('chat_container');
/** /**
* Toggles contact list panel. * Toggles contact list panel.
*/ */
UI.toggleContactList = () => UI.toggleSidePanel("contacts_container"); UI.toggleContactList = () => UI.toggleSidePanel('contacts_container');
/** /**
* Toggles the given side panel. * Toggles the given side panel.
@ -573,7 +631,7 @@ UI.toggleSidePanel = sidePanelId => SideContainerToggler.toggle(sidePanelId);
/** /**
* Handle new user display name. * Handle new user display name.
*/ */
UI.inputDisplayNameHandler = function (newDisplayName) { UI.inputDisplayNameHandler = function(newDisplayName) {
eventEmitter.emit(UIEvents.NICKNAME_CHANGED, newDisplayName); eventEmitter.emit(UIEvents.NICKNAME_CHANGED, newDisplayName);
}; };
@ -588,7 +646,8 @@ UI.inputDisplayNameHandler = function (newDisplayName) {
* @param {number} timeout - The time to show the popup * @param {number} timeout - The time to show the popup
* @returns {void} * @returns {void}
*/ */
UI.showCustomToolbarPopup = function (buttonName, popupID, show, timeout) { // eslint-disable-next-line max-params
UI.showCustomToolbarPopup = function(buttonName, popupID, show, timeout) {
const action = show const action = show
? setButtonPopupTimeout(buttonName, popupID, timeout) ? setButtonPopupTimeout(buttonName, popupID, timeout)
: clearButtonPopup(buttonName); : clearButtonPopup(buttonName);
@ -601,7 +660,7 @@ UI.showCustomToolbarPopup = function (buttonName, popupID, show, timeout) {
* @param jid the jid for the remote video * @param jid the jid for the remote video
* @returns the video type video or screen. * @returns the video type video or screen.
*/ */
UI.getRemoteVideoType = function (jid) { UI.getRemoteVideoType = function(jid) {
return VideoLayout.getRemoteVideoType(jid); return VideoLayout.getRemoteVideoType(jid);
}; };
@ -609,17 +668,19 @@ UI.getRemoteVideoType = function (jid) {
UI.showLoginPopup = function(callback) { UI.showLoginPopup = function(callback) {
logger.log('password is required'); logger.log('password is required');
let message = ( const message
`<input name="username" type="text" = `<input name="username" type="text"
placeholder="user@domain.net" placeholder="user@domain.net"
class="input-control" autofocus> class="input-control" autofocus>
<input name="password" type="password" <input name="password" type="password"
data-i18n="[placeholder]dialog.userPassword" data-i18n="[placeholder]dialog.userPassword"
class="input-control" class="input-control"
placeholder="user password">` placeholder="user password">`
);
let submitFunction = (e, v, m, f) => { ;
// eslint-disable-next-line max-params
const submitFunction = (e, v, m, f) => {
if (v) { if (v) {
if (f.username && f.password) { if (f.username && f.password) {
callback(f.username, f.password); callback(f.username, f.password);
@ -628,7 +689,7 @@ UI.showLoginPopup = function(callback) {
}; };
messageHandler.openTwoButtonDialog({ messageHandler.openTwoButtonDialog({
titleKey : "dialog.passwordRequired", titleKey: 'dialog.passwordRequired',
msgString: message, msgString: message,
leftButtonKey: 'dialog.Ok', leftButtonKey: 'dialog.Ok',
submitFunction, submitFunction,
@ -636,14 +697,15 @@ UI.showLoginPopup = function(callback) {
}); });
}; };
UI.askForNickname = function () { UI.askForNickname = function() {
// eslint-disable-next-line no-alert
return window.prompt('Your nickname (optional)'); return window.prompt('Your nickname (optional)');
}; };
/** /**
* Sets muted audio state for participant * Sets muted audio state for participant
*/ */
UI.setAudioMuted = function (id, muted) { UI.setAudioMuted = function(id, muted) {
VideoLayout.onAudioMute(id, muted); VideoLayout.onAudioMute(id, muted);
if (APP.conference.isLocalId(id)) { if (APP.conference.isLocalId(id)) {
APP.conference.updateAudioIconEnabled(); APP.conference.updateAudioIconEnabled();
@ -653,7 +715,7 @@ UI.setAudioMuted = function (id, muted) {
/** /**
* Sets muted video state for participant * Sets muted video state for participant
*/ */
UI.setVideoMuted = function (id, muted) { UI.setVideoMuted = function(id, muted) {
VideoLayout.onVideoMute(id, muted); VideoLayout.onVideoMute(id, muted);
if (APP.conference.isLocalId(id)) { if (APP.conference.isLocalId(id)) {
APP.conference.updateVideoIconEnabled(); APP.conference.updateVideoIconEnabled();
@ -674,7 +736,7 @@ UI.updateAllVideos = () => VideoLayout.updateAllVideos();
* @param type the type of the event we're listening for * @param type the type of the event we're listening for
* @param listener a function that would be called when notified * @param listener a function that would be called when notified
*/ */
UI.addListener = function (type, listener) { UI.addListener = function(type, listener) {
eventEmitter.on(type, listener); eventEmitter.on(type, listener);
}; };
@ -684,7 +746,7 @@ UI.addListener = function (type, listener) {
* @param type the type of the event we're listening for * @param type the type of the event we're listening for
* @param listener the listener we want to remove * @param listener the listener we want to remove
*/ */
UI.removeListener = function (type, listener) { UI.removeListener = function(type, listener) {
eventEmitter.removeListener(type, listener); eventEmitter.removeListener(type, listener);
}; };
@ -696,14 +758,15 @@ UI.removeListener = function (type, listener) {
*/ */
UI.emitEvent = (type, ...options) => eventEmitter.emit(type, ...options); UI.emitEvent = (type, ...options) => eventEmitter.emit(type, ...options);
UI.clickOnVideo = function (videoNumber) { UI.clickOnVideo = function(videoNumber) {
let videos = $("#remoteVideos .videocontainer:not(#mixedstream)"); const videos = $('#remoteVideos .videocontainer:not(#mixedstream)');
let videosLength = videos.length; const videosLength = videos.length;
if(videosLength <= videoNumber) { if (videosLength <= videoNumber) {
return; return;
} }
let videoIndex = videoNumber === 0 ? 0 : videosLength - videoNumber; const videoIndex = videoNumber === 0 ? 0 : videosLength - videoNumber;
videos[videoIndex].click(); videos[videoIndex].click();
}; };
@ -730,7 +793,7 @@ function changeAvatar(id, avatarUrl) {
* @param {string} id user id * @param {string} id user id
* @param {string} email user email * @param {string} email user email
*/ */
UI.setUserEmail = function (id, email) { UI.setUserEmail = function(id, email) {
// update avatar // update avatar
Avatar.setUserEmail(id, email); Avatar.setUserEmail(id, email);
@ -745,7 +808,7 @@ UI.setUserEmail = function (id, email) {
* @param {string} id user id * @param {string} id user id
* @param {string} avatarId user's avatar id * @param {string} avatarId user's avatar id
*/ */
UI.setUserAvatarID = function (id, avatarId) { UI.setUserAvatarID = function(id, avatarId) {
// update avatar // update avatar
Avatar.setUserAvatarID(id, avatarId); Avatar.setUserAvatarID(id, avatarId);
@ -757,7 +820,7 @@ UI.setUserAvatarID = function (id, avatarId) {
* @param {string} id user id * @param {string} id user id
* @param {string} url user avatar url * @param {string} url user avatar url
*/ */
UI.setUserAvatarUrl = function (id, url) { UI.setUserAvatarUrl = function(id, url) {
// update avatar // update avatar
Avatar.setUserAvatarUrl(id, url); Avatar.setUserAvatarUrl(id, url);
@ -768,39 +831,40 @@ UI.setUserAvatarUrl = function (id, url) {
* Notify user that connection failed. * Notify user that connection failed.
* @param {string} stropheErrorMsg raw Strophe error message * @param {string} stropheErrorMsg raw Strophe error message
*/ */
UI.notifyConnectionFailed = function (stropheErrorMsg) { UI.notifyConnectionFailed = function(stropheErrorMsg) {
var message; let message;
if (stropheErrorMsg) { if (stropheErrorMsg) {
message = APP.translation.generateTranslationHTML( message = APP.translation.generateTranslationHTML(
"dialog.connectErrorWithMsg", {msg: stropheErrorMsg}); 'dialog.connectErrorWithMsg', { msg: stropheErrorMsg });
} else { } else {
message = APP.translation.generateTranslationHTML( message = APP.translation.generateTranslationHTML(
"dialog.connectError"); 'dialog.connectError');
} }
messageHandler.openDialog("dialog.error", message, true, {}, () => false); messageHandler.openDialog('dialog.error', message, true, {}, () => false);
}; };
/** /**
* Notify user that maximum users limit has been reached. * Notify user that maximum users limit has been reached.
*/ */
UI.notifyMaxUsersLimitReached = function () { UI.notifyMaxUsersLimitReached = function() {
var message = APP.translation.generateTranslationHTML( const message = APP.translation.generateTranslationHTML(
"dialog.maxUsersLimitReached"); 'dialog.maxUsersLimitReached');
messageHandler.openDialog("dialog.error", message, true, {}, () => false); messageHandler.openDialog('dialog.error', message, true, {}, () => false);
}; };
/** /**
* Notify user that he was automatically muted when joned the conference. * Notify user that he was automatically muted when joned the conference.
*/ */
UI.notifyInitiallyMuted = function () { UI.notifyInitiallyMuted = function() {
messageHandler.participantNotification( messageHandler.participantNotification(
null, null,
"notify.mutedTitle", 'notify.mutedTitle',
"connected", 'connected',
"notify.muted", 'notify.muted',
null, null,
120000); 120000);
}; };
@ -809,11 +873,11 @@ UI.notifyInitiallyMuted = function () {
* Mark user as dominant speaker. * Mark user as dominant speaker.
* @param {string} id user id * @param {string} id user id
*/ */
UI.markDominantSpeaker = function (id) { UI.markDominantSpeaker = function(id) {
VideoLayout.onDominantSpeakerChanged(id); VideoLayout.onDominantSpeakerChanged(id);
}; };
UI.handleLastNEndpoints = function (leavingIds, enteringIds) { UI.handleLastNEndpoints = function(leavingIds, enteringIds) {
VideoLayout.onLastNEndpointsChanged(leavingIds, enteringIds); VideoLayout.onLastNEndpointsChanged(leavingIds, enteringIds);
}; };
@ -822,7 +886,7 @@ UI.handleLastNEndpoints = function (leavingIds, enteringIds) {
* *
* @param {string} id the id of remote participant(MUC jid) * @param {string} id the id of remote participant(MUC jid)
*/ */
UI.participantConnectionStatusChanged = function (id) { UI.participantConnectionStatusChanged = function(id) {
VideoLayout.onParticipantConnectionStatusChanged(id); VideoLayout.onParticipantConnectionStatusChanged(id);
}; };
@ -854,7 +918,7 @@ UI.updateDesktopSharingButtons
/** /**
* Hide connection quality statistics from UI. * Hide connection quality statistics from UI.
*/ */
UI.hideStats = function () { UI.hideStats = function() {
VideoLayout.hideStats(); VideoLayout.hideStats();
}; };
@ -862,7 +926,7 @@ UI.hideStats = function () {
* Mark video as interrupted or not. * Mark video as interrupted or not.
* @param {boolean} interrupted if video is interrupted * @param {boolean} interrupted if video is interrupted
*/ */
UI.markVideoInterrupted = function (interrupted) { UI.markVideoInterrupted = function(interrupted) {
if (interrupted) { if (interrupted) {
VideoLayout.onVideoInterrupted(); VideoLayout.onVideoInterrupted();
} else { } else {
@ -877,32 +941,34 @@ UI.markVideoInterrupted = function (interrupted) {
* @param {string} message message text * @param {string} message message text
* @param {number} stamp timestamp when message was created * @param {number} stamp timestamp when message was created
*/ */
UI.addMessage = function (from, displayName, message, stamp) { // eslint-disable-next-line max-params
UI.addMessage = function(from, displayName, message, stamp) {
Chat.updateChatConversation(from, displayName, message, stamp); Chat.updateChatConversation(from, displayName, message, stamp);
}; };
UI.updateDTMFSupport UI.updateDTMFSupport
= isDTMFSupported => APP.store.dispatch(showDialPadButton(isDTMFSupported)); = isDTMFSupported => APP.store.dispatch(showDialPadButton(isDTMFSupported));
UI.updateRecordingState = function (state) { UI.updateRecordingState = function(state) {
Recording.updateRecordingState(state); Recording.updateRecordingState(state);
}; };
UI.notifyTokenAuthFailed = function () { UI.notifyTokenAuthFailed = function() {
messageHandler.showError( "dialog.tokenAuthFailedTitle", messageHandler.showError('dialog.tokenAuthFailedTitle',
"dialog.tokenAuthFailed"); 'dialog.tokenAuthFailed');
}; };
UI.notifyInternalError = function () { UI.notifyInternalError = function() {
messageHandler.showError( "dialog.internalErrorTitle", messageHandler.showError('dialog.internalErrorTitle',
"dialog.internalError"); 'dialog.internalError');
}; };
UI.notifyFocusDisconnected = function (focus, retrySec) { UI.notifyFocusDisconnected = function(focus, retrySec) {
messageHandler.participantNotification( messageHandler.participantNotification(
null, "notify.focus", null, 'notify.focus',
'disconnected', "notify.focusFail", 'disconnected', 'notify.focusFail',
{component: focus, ms: retrySec} { component: focus,
ms: retrySec }
); );
}; };
@ -911,9 +977,9 @@ UI.notifyFocusDisconnected = function (focus, retrySec) {
* @param {boolean} isAuthEnabled if authentication is enabled * @param {boolean} isAuthEnabled if authentication is enabled
* @param {string} [login] current login * @param {string} [login] current login
*/ */
UI.updateAuthInfo = function (isAuthEnabled, login) { UI.updateAuthInfo = function(isAuthEnabled, login) {
let showAuth = isAuthEnabled && UIUtil.isAuthenticationEnabled(); const showAuth = isAuthEnabled && UIUtil.isAuthenticationEnabled();
let loggedIn = !!login; const loggedIn = Boolean(login);
Profile.showAuthenticationButtons(showAuth); Profile.showAuthenticationButtons(showAuth);
@ -925,7 +991,7 @@ UI.updateAuthInfo = function (isAuthEnabled, login) {
} }
}; };
UI.onStartMutedChanged = function (startAudioMuted, startVideoMuted) { UI.onStartMutedChanged = function(startAudioMuted, startVideoMuted) {
SettingsMenu.updateStartMutedBox(startAudioMuted, startVideoMuted); SettingsMenu.updateStartMutedBox(startAudioMuted, startVideoMuted);
}; };
@ -935,7 +1001,7 @@ UI.onStartMutedChanged = function (startAudioMuted, startVideoMuted) {
* @param {boolean} isRaisedHand indicates the current state of the * @param {boolean} isRaisedHand indicates the current state of the
* "raised hand" * "raised hand"
*/ */
UI.onLocalRaiseHandChanged = function (isRaisedHand) { UI.onLocalRaiseHandChanged = function(isRaisedHand) {
eventEmitter.emit(UIEvents.LOCAL_RAISE_HAND_CHANGED, isRaisedHand); eventEmitter.emit(UIEvents.LOCAL_RAISE_HAND_CHANGED, isRaisedHand);
}; };
@ -943,7 +1009,7 @@ UI.onLocalRaiseHandChanged = function (isRaisedHand) {
* Update list of available physical devices. * Update list of available physical devices.
* @param {object[]} devices new list of available devices * @param {object[]} devices new list of available devices
*/ */
UI.onAvailableDevicesChanged = function (devices) { UI.onAvailableDevicesChanged = function(devices) {
APP.store.dispatch(updateDeviceList(devices)); APP.store.dispatch(updateDeviceList(devices));
APP.conference.updateAudioIconEnabled(); APP.conference.updateAudioIconEnabled();
APP.conference.updateVideoIconEnabled(); APP.conference.updateVideoIconEnabled();
@ -953,7 +1019,7 @@ UI.onAvailableDevicesChanged = function (devices) {
* Returns the id of the current video shown on large. * Returns the id of the current video shown on large.
* Currently used by tests (torture). * Currently used by tests (torture).
*/ */
UI.getLargeVideoID = function () { UI.getLargeVideoID = function() {
return VideoLayout.getLargeVideoID(); return VideoLayout.getLargeVideoID();
}; };
@ -961,7 +1027,7 @@ UI.getLargeVideoID = function () {
* Returns the current video shown on large. * Returns the current video shown on large.
* Currently used by tests (torture). * Currently used by tests (torture).
*/ */
UI.getLargeVideo = function () { UI.getLargeVideo = function() {
return VideoLayout.getLargeVideo(); return VideoLayout.getLargeVideo();
}; };
@ -977,11 +1043,11 @@ UI.isPinned = userId => VideoLayout.getPinnedId() === userId;
/** /**
* Shows dialog with a link to FF extension. * Shows dialog with a link to FF extension.
*/ */
UI.showExtensionRequiredDialog = function (url) { UI.showExtensionRequiredDialog = function(url) {
messageHandler.openMessageDialog( messageHandler.openMessageDialog(
"dialog.extensionRequired", 'dialog.extensionRequired',
"[html]dialog.firefoxExtensionPrompt", '[html]dialog.firefoxExtensionPrompt',
{url: url}); { url });
}; };
/** /**
@ -989,25 +1055,25 @@ UI.showExtensionRequiredDialog = function (url) {
* 2 button dialog with buttons - cancel and go to web store. * 2 button dialog with buttons - cancel and go to web store.
* @param url {string} the url of the extension. * @param url {string} the url of the extension.
*/ */
UI.showExtensionExternalInstallationDialog = function (url) { UI.showExtensionExternalInstallationDialog = function(url) {
let openedWindow = null; let openedWindow = null;
let submitFunction = function(e,v){ const submitFunction = function(e, v) {
if (v) { if (v) {
e.preventDefault(); e.preventDefault();
if (openedWindow === null || openedWindow.closed) { if (openedWindow === null || openedWindow.closed) {
openedWindow openedWindow
= window.open( = window.open(
url, url,
"extension_store_window", 'extension_store_window',
"resizable,scrollbars=yes,status=1"); 'resizable,scrollbars=yes,status=1');
} else { } else {
openedWindow.focus(); openedWindow.focus();
} }
} }
}; };
let closeFunction = function (e, v) { const closeFunction = function(e, v) {
if (openedWindow) { if (openedWindow) {
// Ideally we would close the popup, but this does not seem to work // Ideally we would close the popup, but this does not seem to work
// on Chrome. Leaving it uncommented in case it could work // on Chrome. Leaving it uncommented in case it could work
@ -1037,14 +1103,14 @@ UI.showExtensionExternalInstallationDialog = function (url) {
* @param callback {function} function to be executed after user clicks * @param callback {function} function to be executed after user clicks
* the install button - it should make another attempt to install the extension. * the install button - it should make another attempt to install the extension.
*/ */
UI.showExtensionInlineInstallationDialog = function (callback) { UI.showExtensionInlineInstallationDialog = function(callback) {
let submitFunction = function(e,v){ const submitFunction = function(e, v) {
if (v) { if (v) {
callback(); callback();
} }
}; };
let closeFunction = function (e, v) { const closeFunction = function(e, v) {
if (!v) { if (!v) {
eventEmitter.emit(UIEvents.EXTERNAL_INSTALLATION_CANCELED); eventEmitter.emit(UIEvents.EXTERNAL_INSTALLATION_CANCELED);
} }
@ -1067,7 +1133,7 @@ UI.showExtensionInlineInstallationDialog = function (callback) {
* acquiring an audio stream. * acquiring an audio stream.
* @returns {void} * @returns {void}
*/ */
UI.showMicErrorNotification = function (micError) { UI.showMicErrorNotification = function(micError) {
if (!micError) { if (!micError) {
return; return;
} }
@ -1103,7 +1169,7 @@ UI.showMicErrorNotification = function (micError) {
* acquiring a video stream. * acquiring a video stream.
* @returns {void} * @returns {void}
*/ */
UI.showCameraErrorNotification = function (cameraError) { UI.showCameraErrorNotification = function(cameraError) {
if (!cameraError) { if (!cameraError) {
return; return;
} }
@ -1112,8 +1178,8 @@ UI.showCameraErrorNotification = function (cameraError) {
const persistenceKey = `doNotShowErrorAgain-camera-${name}`; const persistenceKey = `doNotShowErrorAgain-camera-${name}`;
const cameraJitsiTrackErrorMsg = const cameraJitsiTrackErrorMsg
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[name]; = JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[name];
const cameraErrorMsg = cameraJitsiTrackErrorMsg const cameraErrorMsg = cameraJitsiTrackErrorMsg
|| JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP || JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
.camera[JitsiTrackErrors.GENERAL]; .camera[JitsiTrackErrors.GENERAL];
@ -1136,14 +1202,14 @@ UI.showCameraErrorNotification = function (cameraError) {
* Shows error dialog that informs the user that no data is received from the * Shows error dialog that informs the user that no data is received from the
* device. * device.
*/ */
UI.showTrackNotWorkingDialog = function (stream) { UI.showTrackNotWorkingDialog = function(stream) {
messageHandler.openMessageDialog( messageHandler.openMessageDialog(
"dialog.error", 'dialog.error',
stream.isAudioTrack()? "dialog.micNotSendingData" : stream.isAudioTrack() ? 'dialog.micNotSendingData'
"dialog.cameraNotSendingData"); : 'dialog.cameraNotSendingData');
}; };
UI.updateDevicesAvailability = function (id, devices) { UI.updateDevicesAvailability = function(id, devices) {
VideoLayout.setDeviceAvailabilityIcons(id, devices); VideoLayout.setDeviceAvailabilityIcons(id, devices);
}; };
@ -1153,9 +1219,10 @@ UI.updateDevicesAvailability = function (id, devices) {
* @param {string} url video url * @param {string} url video url
* @param {string} attributes * @param {string} attributes
*/ */
UI.onSharedVideoStart = function (id, url, attributes) { UI.onSharedVideoStart = function(id, url, attributes) {
if (sharedVideoManager) if (sharedVideoManager) {
sharedVideoManager.onSharedVideoStart(id, url, attributes); sharedVideoManager.onSharedVideoStart(id, url, attributes);
}
}; };
/** /**
@ -1164,9 +1231,10 @@ UI.onSharedVideoStart = function (id, url, attributes) {
* @param {string} url video url * @param {string} url video url
* @param {string} attributes * @param {string} attributes
*/ */
UI.onSharedVideoUpdate = function (id, url, attributes) { UI.onSharedVideoUpdate = function(id, url, attributes) {
if (sharedVideoManager) if (sharedVideoManager) {
sharedVideoManager.onSharedVideoUpdate(id, url, attributes); sharedVideoManager.onSharedVideoUpdate(id, url, attributes);
}
}; };
/** /**
@ -1174,9 +1242,10 @@ UI.onSharedVideoUpdate = function (id, url, attributes) {
* @param {string} id the id of the sender of the command * @param {string} id the id of the sender of the command
* @param {string} attributes * @param {string} attributes
*/ */
UI.onSharedVideoStop = function (id, attributes) { UI.onSharedVideoStop = function(id, attributes) {
if (sharedVideoManager) if (sharedVideoManager) {
sharedVideoManager.onSharedVideoStop(id, attributes); sharedVideoManager.onSharedVideoStop(id, attributes);
}
}; };
/** /**
@ -1211,50 +1280,6 @@ UI.setLocalRemoteControlActiveChanged = function() {
VideoLayout.setLocalRemoteControlActiveChanged(); VideoLayout.setLocalRemoteControlActiveChanged();
}; };
const UIListeners = new Map([
[
UIEvents.ETHERPAD_CLICKED,
() => etherpadManager && etherpadManager.toggleEtherpad()
], [
UIEvents.SHARED_VIDEO_CLICKED,
() => sharedVideoManager && sharedVideoManager.toggleSharedVideo()
], [
UIEvents.TOGGLE_FULLSCREEN,
UI.toggleFullScreen
], [
UIEvents.TOGGLE_CHAT,
UI.toggleChat
], [
UIEvents.TOGGLE_SETTINGS,
() => {
// Opening of device selection is special-cased as it is a dialog
// opened through a button in settings and not directly displayed in
// settings itself. As it is not useful to only have a settings menu
// with a button to open a dialog, open the dialog directly instead.
if (interfaceConfig.SETTINGS_SECTIONS.length === 1
&& UIUtil.isSettingEnabled('devices')) {
APP.store.dispatch(openDeviceSelectionDialog());
} else {
UI.toggleSidePanel("settings_container");
}
}
], [
UIEvents.TOGGLE_CONTACT_LIST,
UI.toggleContactList
], [
UIEvents.TOGGLE_PROFILE,
() => {
UI.toggleSidePanel('profile_container');
}
], [
UIEvents.TOGGLE_FILMSTRIP,
UI.handleToggleFilmstrip
], [
UIEvents.FOLLOW_ME_ENABLED,
enabled => (followMeHandler && followMeHandler.enableFollowMe(enabled))
]
]);
// TODO: Export every function separately. For now there is no point of doing // TODO: Export every function separately. For now there is no point of doing
// this because we are importing everything. // this because we are importing everything.
export default UI; export default UI;

View File

@ -7,4 +7,4 @@
* *
* @type {{FEEDBACK_REQUEST_IN_PROGRESS: string}} * @type {{FEEDBACK_REQUEST_IN_PROGRESS: string}}
*/ */
export const FEEDBACK_REQUEST_IN_PROGRESS = "FeedbackRequestInProgress"; export const FEEDBACK_REQUEST_IN_PROGRESS = 'FeedbackRequestInProgress';

View File

@ -1,6 +1,6 @@
/* global interfaceConfig */ /* global interfaceConfig */
import UIUtil from "../util/UIUtil"; import UIUtil from '../util/UIUtil';
/** /**
* Responsible for drawing audio levels. * Responsible for drawing audio levels.
@ -19,29 +19,31 @@ const AudioLevels = {
_setDotLevel(elementID, index, opacity) { _setDotLevel(elementID, index, opacity) {
let audioSpan let audioSpan
= document.getElementById(elementID) = document.getElementById(elementID)
.getElementsByClassName("audioindicator"); .getElementsByClassName('audioindicator');
// Make sure the audio span is still around. // Make sure the audio span is still around.
if (audioSpan && audioSpan.length > 0) if (audioSpan && audioSpan.length > 0) {
audioSpan = audioSpan[0]; audioSpan = audioSpan[0];
else } else {
return; return;
}
let audioTopDots const audioTopDots
= audioSpan.getElementsByClassName("audiodot-top"); = audioSpan.getElementsByClassName('audiodot-top');
let audioDotMiddle const audioDotMiddle
= audioSpan.getElementsByClassName("audiodot-middle"); = audioSpan.getElementsByClassName('audiodot-middle');
let audioBottomDots const audioBottomDots
= audioSpan.getElementsByClassName("audiodot-bottom"); = audioSpan.getElementsByClassName('audiodot-bottom');
// First take care of the middle dot case. // First take care of the middle dot case.
if (index === 0){ if (index === 0) {
audioDotMiddle[0].style.opacity = opacity; audioDotMiddle[0].style.opacity = opacity;
return; return;
} }
// Index > 0 : we are setting non-middle dots. // Index > 0 : we are setting non-middle dots.
index--; index--;// eslint-disable-line no-param-reassign
audioBottomDots[index].style.opacity = opacity; audioBottomDots[index].style.opacity = opacity;
audioTopDots[this.sideDotsCount - index - 1].style.opacity = opacity; audioTopDots[this.sideDotsCount - index - 1].style.opacity = opacity;
}, },
@ -52,19 +54,21 @@ const AudioLevels = {
* @param audioLevel the new audio level to set. * @param audioLevel the new audio level to set.
*/ */
updateLargeVideoAudioLevel(elementId, audioLevel) { updateLargeVideoAudioLevel(elementId, audioLevel) {
let element = document.getElementById(elementId); const element = document.getElementById(elementId);
if(!UIUtil.isVisible(element)) if (!UIUtil.isVisible(element)) {
return; return;
}
let level = parseFloat(audioLevel); let level = parseFloat(audioLevel);
level = isNaN(level) ? 0 : level; level = isNaN(level) ? 0 : level;
let shadowElement = element.getElementsByClassName("dynamic-shadow"); let shadowElement = element.getElementsByClassName('dynamic-shadow');
if (shadowElement && shadowElement.length > 0) if (shadowElement && shadowElement.length > 0) {
shadowElement = shadowElement[0]; shadowElement = shadowElement[0];
}
shadowElement.style.boxShadow = this._updateLargeVideoShadow(level); shadowElement.style.boxShadow = this._updateLargeVideoShadow(level);
}, },
@ -83,7 +87,7 @@ const AudioLevels = {
// External circle audio level. // External circle audio level.
const ext = { const ext = {
level: (int.level * scale * level + int.level).toFixed(0), level: ((int.level * scale * level) + int.level).toFixed(0),
color: interfaceConfig.AUDIO_LEVEL_SECONDARY_COLOR color: interfaceConfig.AUDIO_LEVEL_SECONDARY_COLOR
}; };
@ -94,8 +98,8 @@ const AudioLevels = {
ext.blur = ext.level ? 6 : 0; ext.blur = ext.level ? 6 : 0;
return [ return [
`0 0 ${ int.blur }px ${ int.level }px ${ int.color }`, `0 0 ${int.blur}px ${int.level}px ${int.color}`,
`0 0 ${ ext.blur }px ${ ext.level }px ${ ext.color }` `0 0 ${ext.blur}px ${ext.level}px ${ext.color}`
].join(', '); ].join(', ');
} }
}; };

View File

@ -9,14 +9,14 @@ import UIUtil from '../util/UIUtil';
import LoginDialog from './LoginDialog'; import LoginDialog from './LoginDialog';
const logger = require("jitsi-meet-logger").getLogger(__filename); const logger = require('jitsi-meet-logger').getLogger(__filename);
let externalAuthWindow; let externalAuthWindow;
let authRequiredDialog; let authRequiredDialog;
let isTokenAuthEnabled const isTokenAuthEnabled
= typeof config.tokenAuthUrl === "string" && config.tokenAuthUrl.length; = typeof config.tokenAuthUrl === 'string' && config.tokenAuthUrl.length;
let getTokenAuthUrl const getTokenAuthUrl
= JitsiMeetJS.util.AuthUtil.getTokenAuthUrl.bind(null, config.tokenAuthUrl); = JitsiMeetJS.util.AuthUtil.getTokenAuthUrl.bind(null, config.tokenAuthUrl);
/** /**
@ -26,23 +26,25 @@ let getTokenAuthUrl
* @param {JitsiConference} room * @param {JitsiConference} room
* @param {string} [lockPassword] password to use if the conference is locked * @param {string} [lockPassword] password to use if the conference is locked
*/ */
function doExternalAuth (room, lockPassword) { function doExternalAuth(room, lockPassword) {
if (externalAuthWindow) { if (externalAuthWindow) {
externalAuthWindow.focus(); externalAuthWindow.focus();
return; return;
} }
if (room.isJoined()) { if (room.isJoined()) {
let getUrl; let getUrl;
if (isTokenAuthEnabled) { if (isTokenAuthEnabled) {
getUrl = Promise.resolve(getTokenAuthUrl(room.getName(), true)); getUrl = Promise.resolve(getTokenAuthUrl(room.getName(), true));
initJWTTokenListener(room); initJWTTokenListener(room);
} else { } else {
getUrl = room.getExternalAuthUrl(true); getUrl = room.getExternalAuthUrl(true);
} }
getUrl.then(function (url) { getUrl.then(url => {
externalAuthWindow = LoginDialog.showExternalAuthDialog( externalAuthWindow = LoginDialog.showExternalAuthDialog(
url, url,
function () { () => {
externalAuthWindow = null; externalAuthWindow = null;
if (!isTokenAuthEnabled) { if (!isTokenAuthEnabled) {
room.join(lockPassword); room.join(lockPassword);
@ -50,14 +52,10 @@ function doExternalAuth (room, lockPassword) {
} }
); );
}); });
} else if (isTokenAuthEnabled) {
redirectToTokenAuthService(room.getName());
} else { } else {
// If conference has not been started yet room.getExternalAuthUrl().then(UIUtil.redirect);
// then redirect to login page
if (isTokenAuthEnabled) {
redirectToTokenAuthService(room.getName());
} else {
room.getExternalAuthUrl().then(UIUtil.redirect);
}
} }
} }
@ -77,61 +75,80 @@ function redirectToTokenAuthService(roomName) {
* @param room the name fo the conference room. * @param room the name fo the conference room.
*/ */
function initJWTTokenListener(room) { function initJWTTokenListener(room) {
var listener = function ({ data, source }) { /**
*
*/
function listener({ data, source }) {
if (externalAuthWindow !== source) { if (externalAuthWindow !== source) {
logger.warn("Ignored message not coming " + logger.warn('Ignored message not coming '
"from external authnetication window"); + 'from external authnetication window');
return; return;
} }
let jwt; let jwt;
if (data && (jwt = data.jwtToken)) { if (data && (jwt = data.jwtToken)) {
logger.info("Received JSON Web Token (JWT):", jwt); logger.info('Received JSON Web Token (JWT):', jwt);
APP.store.dispatch(setJWT(jwt)); APP.store.dispatch(setJWT(jwt));
var roomName = room.getName(); const roomName = room.getName();
openConnection({retry: false, roomName: roomName })
.then(function (connection) { openConnection({
// Start new connection retry: false,
let newRoom = connection.initJitsiConference( roomName
roomName, APP.conference._getConferenceOptions()); }).then(connection => {
// Authenticate from the new connection to get // Start new connection
// the session-ID from the focus, which wil then be used const newRoom = connection.initJitsiConference(
// to upgrade current connection's user role roomName, APP.conference._getConferenceOptions());
newRoom.room.moderator.authenticate().then(function () {
connection.disconnect(); // Authenticate from the new connection to get
// At this point we'll have session-ID stored in // the session-ID from the focus, which wil then be used
// the settings. It wil be used in the call below // to upgrade current connection's user role
// to upgrade user's role
room.room.moderator.authenticate() newRoom.room.moderator.authenticate()
.then(function () { .then(() => {
logger.info("User role upgrade done !"); connection.disconnect();
unregister();
}).catch(function (err, errCode) { // At this point we'll have session-ID stored in
logger.error( // the settings. It wil be used in the call below
"Authentication failed: ", err, errCode); // to upgrade user's role
unregister(); room.room.moderator.authenticate()
}); .then(() => {
}).catch(function (error, code) { logger.info('User role upgrade done !');
unregister(); // eslint-disable-line no-use-before-define
connection.disconnect(); unregister();
logger.error( })
'Authentication failed on the new connection', .catch((err, errCode) => {
error, code); logger.error('Authentication failed: ',
}); err, errCode);
}, function (err) { unregister();
});
})
.catch((error, code) => {
unregister(); unregister();
logger.error("Failed to open new connection", err); connection.disconnect();
logger.error(
'Authentication failed on the new connection',
error, code);
}); });
}, err => {
unregister();
logger.error('Failed to open new connection', err);
});
} }
}; }
var unregister = function () {
window.removeEventListener("message", listener); /**
}; *
*/
function unregister() {
window.removeEventListener('message', listener);
}
if (window.addEventListener) { if (window.addEventListener) {
window.addEventListener("message", listener, false); window.addEventListener('message', listener, false);
} }
} }
@ -183,7 +200,7 @@ function doXmppAuth(room, lockPassword) {
* @param {JitsiConference} room * @param {JitsiConference} room
* @param {string} [lockPassword] password to use if the conference is locked * @param {string} [lockPassword] password to use if the conference is locked
*/ */
function authenticate (room, lockPassword) { function authenticate(room, lockPassword) {
if (isTokenAuthEnabled || room.isExternalAuthEnabled()) { if (isTokenAuthEnabled || room.isExternalAuthEnabled()) {
doExternalAuth(room, lockPassword); doExternalAuth(room, lockPassword);
} else { } else {
@ -198,10 +215,10 @@ function authenticate (room, lockPassword) {
* @param {string} [lockPassword] password to use if the conference is locked * @param {string} [lockPassword] password to use if the conference is locked
* @returns {Promise} * @returns {Promise}
*/ */
function logout (room) { function logout(room) {
return new Promise(function (resolve) { return new Promise(resolve => {
room.room.moderator.logout(resolve); room.room.moderator.logout(resolve);
}).then(function (url) { }).then(url => {
// de-authenticate conference on the fly // de-authenticate conference on the fly
if (room.isJoined()) { if (room.isJoined()) {
room.join(); room.join();
@ -241,14 +258,17 @@ function closeAuth() {
} }
} }
/**
*
*/
function showXmppPasswordPrompt(roomName, connect) { function showXmppPasswordPrompt(roomName, connect) {
return new Promise(function (resolve, reject) { return new Promise((resolve, reject) => {
let authDialog = LoginDialog.showAuthDialog( const authDialog = LoginDialog.showAuthDialog(
function (id, password) { (id, password) => {
connect(id, password, roomName).then(function (connection) { connect(id, password, roomName).then(connection => {
authDialog.close(); authDialog.close();
resolve(connection); resolve(connection);
}, function (err) { }, err => {
if (err === JitsiConnectionErrors.PASSWORD_REQUIRED) { if (err === JitsiConnectionErrors.PASSWORD_REQUIRED) {
authDialog.displayError(err); authDialog.displayError(err);
} else { } else {
@ -275,9 +295,10 @@ function requestAuth(roomName, connect) {
if (isTokenAuthEnabled) { if (isTokenAuthEnabled) {
// This Promise never resolves as user gets redirected to another URL // This Promise never resolves as user gets redirected to another URL
return new Promise(() => redirectToTokenAuthService(roomName)); return new Promise(() => redirectToTokenAuthService(roomName));
} else {
return showXmppPasswordPrompt(roomName, connect);
} }
return showXmppPasswordPrompt(roomName, connect);
} }
export default { export default {

View File

@ -10,9 +10,9 @@ import {
* @returns {string} html string * @returns {string} html string
*/ */
function getPasswordInputHtml() { function getPasswordInputHtml() {
let placeholder = config.hosts.authdomain const placeholder = config.hosts.authdomain
? "user identity" ? 'user identity'
: "user@domain.net"; : 'user@domain.net';
return ` return `
<input name="username" type="text" <input name="username" type="text"
@ -29,7 +29,7 @@ function getPasswordInputHtml() {
*/ */
function cancelButton() { function cancelButton() {
return { return {
title: APP.translation.generateTranslationHTML("dialog.Cancel"), title: APP.translation.generateTranslationHTML('dialog.Cancel'),
value: false value: false
}; };
} }
@ -46,14 +46,14 @@ function cancelButton() {
* @param {function} [cancelCallback] callback to invoke if user canceled. * @param {function} [cancelCallback] callback to invoke if user canceled.
*/ */
function LoginDialog(successCallback, cancelCallback) { function LoginDialog(successCallback, cancelCallback) {
let loginButtons = [{ const loginButtons = [ {
title: APP.translation.generateTranslationHTML("dialog.Ok"), title: APP.translation.generateTranslationHTML('dialog.Ok'),
value: true value: true
}]; } ];
let finishedButtons = [{ const finishedButtons = [ {
title: APP.translation.generateTranslationHTML('dialog.retry'), title: APP.translation.generateTranslationHTML('dialog.retry'),
value: 'retry' value: 'retry'
}]; } ];
// show "cancel" button only if cancelCallback provided // show "cancel" button only if cancelCallback provided
if (cancelCallback) { if (cancelCallback) {
@ -61,17 +61,28 @@ function LoginDialog(successCallback, cancelCallback) {
finishedButtons.push(cancelButton()); finishedButtons.push(cancelButton());
} }
const connDialog = APP.UI.messageHandler.openDialogWithStates(
states, // eslint-disable-line no-use-before-define
{
persistent: true,
closeText: ''
},
null
);
const states = { const states = {
login: { login: {
titleKey: 'dialog.passwordRequired', titleKey: 'dialog.passwordRequired',
html: getPasswordInputHtml(), html: getPasswordInputHtml(),
buttons: loginButtons, buttons: loginButtons,
focus: ':input:first', focus: ':input:first',
submit: function (e, v, m, f) { // eslint-disable-next-line max-params
submit(e, v, m, f) {
e.preventDefault(); e.preventDefault();
if (v) { if (v) {
let jid = f.username; const jid = f.username;
let password = f.password; const password = f.password;
if (jid && password) { if (jid && password) {
connDialog.goToState('connecting'); connDialog.goToState('connecting');
successCallback(toJid(jid, config.hosts), password); successCallback(toJid(jid, config.hosts), password);
@ -84,16 +95,16 @@ function LoginDialog(successCallback, cancelCallback) {
}, },
connecting: { connecting: {
titleKey: 'dialog.connecting', titleKey: 'dialog.connecting',
html: '<div id="connectionStatus"></div>', html: '<div id="connectionStatus"></div>',
buttons: [], buttons: [],
defaultButton: 0 defaultButton: 0
}, },
finished: { finished: {
titleKey: 'dialog.error', titleKey: 'dialog.error',
html: '<div id="errorMessage"></div>', html: '<div id="errorMessage"></div>',
buttons: finishedButtons, buttons: finishedButtons,
defaultButton: 0, defaultButton: 0,
submit: function (e, v) { submit(e, v) {
e.preventDefault(); e.preventDefault();
if (v === 'retry') { if (v === 'retry') {
connDialog.goToState('login'); connDialog.goToState('login');
@ -105,38 +116,35 @@ function LoginDialog(successCallback, cancelCallback) {
} }
}; };
var connDialog = APP.UI.messageHandler.openDialogWithStates(
states, { persistent: true, closeText: '' }, null
);
/** /**
* Displays error message in 'finished' state which allows either to cancel * Displays error message in 'finished' state which allows either to cancel
* or retry. * or retry.
* @param error the key to the error to be displayed. * @param error the key to the error to be displayed.
* @param options the options to the error message (optional) * @param options the options to the error message (optional)
*/ */
this.displayError = function (error, options) { this.displayError = function(error, options) {
let finishedState = connDialog.getState('finished'); const finishedState = connDialog.getState('finished');
let errorMessageElem = finishedState.find('#errorMessage'); const errorMessageElem = finishedState.find('#errorMessage');
let messageKey; let messageKey;
if (error === JitsiConnectionErrors.PASSWORD_REQUIRED) { if (error === JitsiConnectionErrors.PASSWORD_REQUIRED) {
// this is a password required error, as login window was already // this is a password required error, as login window was already
// open once, this means username or password is wrong // open once, this means username or password is wrong
messageKey = 'dialog.incorrectPassword'; messageKey = 'dialog.incorrectPassword';
} } else {
else {
messageKey = 'dialog.connectErrorWithMsg'; messageKey = 'dialog.connectErrorWithMsg';
if (!options) if (!options) {
options = {}; options = {};// eslint-disable-line no-param-reassign
}
options.msg = error; options.msg = error;
} }
errorMessageElem.attr("data-i18n", messageKey); errorMessageElem.attr('data-i18n', messageKey);
APP.translation.translateElement($(errorMessageElem), options); APP.translation.translateElement($(errorMessageElem), options);
@ -147,18 +155,19 @@ function LoginDialog(successCallback, cancelCallback) {
* Show message as connection status. * Show message as connection status.
* @param {string} messageKey the key to the message * @param {string} messageKey the key to the message
*/ */
this.displayConnectionStatus = function (messageKey) { this.displayConnectionStatus = function(messageKey) {
let connectingState = connDialog.getState('connecting'); const connectingState = connDialog.getState('connecting');
let connectionStatus = connectingState.find('#connectionStatus'); const connectionStatus = connectingState.find('#connectionStatus');
connectionStatus.attr("data-i18n", messageKey);
connectionStatus.attr('data-i18n', messageKey);
APP.translation.translateElement($(connectionStatus)); APP.translation.translateElement($(connectionStatus));
}; };
/** /**
* Closes LoginDialog. * Closes LoginDialog.
*/ */
this.close = function () { this.close = function() {
connDialog.close(); connDialog.close();
}; };
} }
@ -173,7 +182,7 @@ export default {
* *
* @returns {LoginDialog} * @returns {LoginDialog}
*/ */
showAuthDialog: function (successCallback, cancelCallback) { showAuthDialog(successCallback, cancelCallback) {
return new LoginDialog(successCallback, cancelCallback); return new LoginDialog(successCallback, cancelCallback);
}, },
@ -183,15 +192,16 @@ export default {
* @param {function} callback callback to invoke when auth popup is closed. * @param {function} callback callback to invoke when auth popup is closed.
* @returns auth dialog * @returns auth dialog
*/ */
showExternalAuthDialog: function (url, callback) { showExternalAuthDialog(url, callback) {
var dialog = APP.UI.messageHandler.openCenteredPopup( const dialog = APP.UI.messageHandler.openCenteredPopup(
url, 910, 660, url, 910, 660,
// On closed // On closed
callback callback
); );
if (!dialog) { if (!dialog) {
APP.UI.messageHandler.openMessageDialog(null, "dialog.popupError"); APP.UI.messageHandler.openMessageDialog(null, 'dialog.popupError');
} }
return dialog; return dialog;
@ -215,10 +225,10 @@ export default {
const buttonTxt = APP.translation.generateTranslationHTML( const buttonTxt = APP.translation.generateTranslationHTML(
'dialog.IamHost' 'dialog.IamHost'
); );
const buttons = [{ const buttons = [ {
title: buttonTxt, title: buttonTxt,
value: 'authNow' value: 'authNow'
}]; } ];
return APP.UI.messageHandler.openDialog( return APP.UI.messageHandler.openDialog(
'dialog.WaitingForHost', 'dialog.WaitingForHost',

View File

@ -25,7 +25,7 @@
import { getAvatarURL } from '../../../react/features/base/participants'; import { getAvatarURL } from '../../../react/features/base/participants';
let users = {}; const users = {};
export default { export default {
/** /**
@ -34,17 +34,19 @@ export default {
* @param prop {string} name of the prop * @param prop {string} name of the prop
* @param val {string} value to be set * @param val {string} value to be set
*/ */
_setUserProp: function (id, prop, val) { _setUserProp(id, prop, val) {
// FIXME: Fixes the issue with not be able to return avatar for the // FIXME: Fixes the issue with not be able to return avatar for the
// local user when the conference has been left. Maybe there is beter // local user when the conference has been left. Maybe there is beter
// way to solve it. // way to solve it.
if(!id || APP.conference.isLocalId(id)) { if (!id || APP.conference.isLocalId(id)) {
id = "local"; id = 'local';// eslint-disable-line no-param-reassign
} }
if(!val || (users[id] && users[id][prop] === val)) if (!val || (users[id] && users[id][prop] === val)) {
return; return;
if(!users[id]) }
if (!users[id]) {
users[id] = {}; users[id] = {};
}
users[id][prop] = val; users[id][prop] = val;
}, },
@ -54,8 +56,8 @@ export default {
* @param id id of the user * @param id id of the user
* @param email email or nickname to be used as a hash * @param email email or nickname to be used as a hash
*/ */
setUserEmail: function (id, email) { setUserEmail(id, email) {
this._setUserProp(id, "email", email); this._setUserProp(id, 'email', email);
}, },
/** /**
@ -64,8 +66,8 @@ export default {
* @param id id of the user * @param id id of the user
* @param url the url for the avatar * @param url the url for the avatar
*/ */
setUserAvatarUrl: function (id, url) { setUserAvatarUrl(id, url) {
this._setUserProp(id, "avatarUrl", url); this._setUserProp(id, 'avatarUrl', url);
}, },
/** /**
@ -73,8 +75,8 @@ export default {
* @param id id of the user * @param id id of the user
* @param avatarId an id to be used for the avatar * @param avatarId an id to be used for the avatar
*/ */
setUserAvatarID: function (id, avatarId) { setUserAvatarID(id, avatarId) {
this._setUserProp(id, "avatarId", avatarId); this._setUserProp(id, 'avatarId', avatarId);
}, },
/** /**
@ -82,10 +84,12 @@ export default {
* identified by its id. * identified by its id.
* @param {string} userId user id * @param {string} userId user id
*/ */
getAvatarUrl: function (userId) { getAvatarUrl(userId) {
let user; let user;
if (!userId || APP.conference.isLocalId(userId)) { if (!userId || APP.conference.isLocalId(userId)) {
user = users.local; user = users.local;
// eslint-disable-next-line no-param-reassign
userId = APP.conference.getMyUserId(); userId = APP.conference.getMyUserId();
} else { } else {
user = users[userId]; user = users[userId];

View File

@ -1,8 +1,8 @@
/* global $ */ /* global $ */
import VideoLayout from "../videolayout/VideoLayout"; import VideoLayout from '../videolayout/VideoLayout';
import LargeContainer from '../videolayout/LargeContainer'; import LargeContainer from '../videolayout/LargeContainer';
import UIEvents from "../../../service/UI/UIEvents"; import UIEvents from '../../../service/UI/UIEvents';
import Filmstrip from '../videolayout/Filmstrip'; import Filmstrip from '../videolayout/Filmstrip';
/** /**
@ -15,14 +15,21 @@ const options = $.param({
useMonospaceFont: false useMonospaceFont: false
}); });
function bubbleIframeMouseMove(iframe){ /**
var existingOnMouseMove = iframe.contentWindow.onmousemove; *
iframe.contentWindow.onmousemove = function(e){ */
if(existingOnMouseMove) existingOnMouseMove(e); function bubbleIframeMouseMove(iframe) {
var evt = document.createEvent("MouseEvents"); const existingOnMouseMove = iframe.contentWindow.onmousemove;
var boundingClientRect = iframe.getBoundingClientRect();
iframe.contentWindow.onmousemove = function(e) {
if (existingOnMouseMove) {
existingOnMouseMove(e);
}
const evt = document.createEvent('MouseEvents');
const boundingClientRect = iframe.getBoundingClientRect();
evt.initMouseEvent( evt.initMouseEvent(
"mousemove", 'mousemove',
true, // bubbles true, // bubbles
false, // not cancelable false, // not cancelable
window, window,
@ -46,27 +53,30 @@ function bubbleIframeMouseMove(iframe){
* Default Etherpad frame width. * Default Etherpad frame width.
*/ */
const DEFAULT_WIDTH = 640; const DEFAULT_WIDTH = 640;
/** /**
* Default Etherpad frame height. * Default Etherpad frame height.
*/ */
const DEFAULT_HEIGHT = 480; const DEFAULT_HEIGHT = 480;
const ETHERPAD_CONTAINER_TYPE = "etherpad"; const ETHERPAD_CONTAINER_TYPE = 'etherpad';
/** /**
* Container for Etherpad iframe. * Container for Etherpad iframe.
*/ */
class Etherpad extends LargeContainer { class Etherpad extends LargeContainer {
/**
constructor (domain, name) { * Creates new Etherpad object
*/
constructor(domain, name) {
super(); super();
const iframe = document.createElement('iframe'); const iframe = document.createElement('iframe');
iframe.id = "etherpadIFrame"; iframe.id = 'etherpadIFrame';
iframe.src = domain + name + '?' + options; iframe.src = `${domain + name}?${options}`;
iframe.frameBorder = 0; iframe.frameBorder = 0;
iframe.scrolling = "no"; iframe.scrolling = 'no';
iframe.width = DEFAULT_WIDTH; iframe.width = DEFAULT_WIDTH;
iframe.height = DEFAULT_HEIGHT; iframe.height = DEFAULT_HEIGHT;
iframe.setAttribute('style', 'visibility: hidden;'); iframe.setAttribute('style', 'visibility: hidden;');
@ -77,15 +87,17 @@ class Etherpad extends LargeContainer {
document.domain = document.domain; document.domain = document.domain;
bubbleIframeMouseMove(iframe); bubbleIframeMouseMove(iframe);
setTimeout(function() { setTimeout(() => {
const doc = iframe.contentDocument; const doc = iframe.contentDocument;
// the iframes inside of the etherpad are // the iframes inside of the etherpad are
// not yet loaded when the etherpad iframe is loaded // not yet loaded when the etherpad iframe is loaded
const outer = doc.getElementsByName("ace_outer")[0]; const outer = doc.getElementsByName('ace_outer')[0];
bubbleIframeMouseMove(outer); bubbleIframeMouseMove(outer);
const inner = doc.getElementsByName("ace_inner")[0]; const inner = doc.getElementsByName('ace_inner')[0];
bubbleIframeMouseMove(inner); bubbleIframeMouseMove(inner);
}, 2000); }, 2000);
}; };
@ -93,47 +105,64 @@ class Etherpad extends LargeContainer {
this.iframe = iframe; this.iframe = iframe;
} }
get isOpen () { /**
return !!this.iframe; *
*/
get isOpen() {
return Boolean(this.iframe);
} }
get container () { /**
*
*/
get container() {
return document.getElementById('etherpad'); return document.getElementById('etherpad');
} }
// eslint-disable-next-line no-unused-vars /**
resize (containerWidth, containerHeight, animate) { *
let height = containerHeight - Filmstrip.getFilmstripHeight(); */
let width = containerWidth; resize(containerWidth, containerHeight) {
const height = containerHeight - Filmstrip.getFilmstripHeight();
const width = containerWidth;
$(this.iframe).width(width).height(height); $(this.iframe)
.width(width)
.height(height);
} }
show () { /**
*
*/
show() {
const $iframe = $(this.iframe); const $iframe = $(this.iframe);
const $container = $(this.container); const $container = $(this.container);
let self = this; const self = this;
return new Promise(resolve => { return new Promise(resolve => {
$iframe.fadeIn(300, function () { $iframe.fadeIn(300, () => {
self.bodyBackground = document.body.style.background; self.bodyBackground = document.body.style.background;
document.body.style.background = '#eeeeee'; document.body.style.background = '#eeeeee';
$iframe.css({visibility: 'visible'}); $iframe.css({ visibility: 'visible' });
$container.css({zIndex: 2}); $container.css({ zIndex: 2 });
resolve(); resolve();
}); });
}); });
} }
hide () { /**
*
*/
hide() {
const $iframe = $(this.iframe); const $iframe = $(this.iframe);
const $container = $(this.container); const $container = $(this.container);
document.body.style.background = this.bodyBackground; document.body.style.background = this.bodyBackground;
return new Promise(resolve => { return new Promise(resolve => {
$iframe.fadeOut(300, function () { $iframe.fadeOut(300, () => {
$iframe.css({visibility: 'hidden'}); $iframe.css({ visibility: 'hidden' });
$container.css({zIndex: 0}); $container.css({ zIndex: 0 });
resolve(); resolve();
}); });
}); });
@ -142,7 +171,7 @@ class Etherpad extends LargeContainer {
/** /**
* @return {boolean} do not switch on dominant speaker event if on stage. * @return {boolean} do not switch on dominant speaker event if on stage.
*/ */
stayOnStage () { stayOnStage() {
return true; return true;
} }
} }
@ -151,9 +180,12 @@ class Etherpad extends LargeContainer {
* Manager of the Etherpad frame. * Manager of the Etherpad frame.
*/ */
export default class EtherpadManager { export default class EtherpadManager {
constructor (domain, name, eventEmitter) { /**
*
*/
constructor(domain, name, eventEmitter) {
if (!domain || !name) { if (!domain || !name) {
throw new Error("missing domain or name"); throw new Error('missing domain or name');
} }
this.domain = domain; this.domain = domain;
@ -162,10 +194,16 @@ export default class EtherpadManager {
this.etherpad = null; this.etherpad = null;
} }
get isOpen () { /**
return !!this.etherpad; *
*/
get isOpen() {
return Boolean(this.etherpad);
} }
/**
*
*/
isVisible() { isVisible() {
return VideoLayout.isLargeContainerTypeVisible(ETHERPAD_CONTAINER_TYPE); return VideoLayout.isLargeContainerTypeVisible(ETHERPAD_CONTAINER_TYPE);
} }
@ -173,7 +211,7 @@ export default class EtherpadManager {
/** /**
* Create new Etherpad frame. * Create new Etherpad frame.
*/ */
openEtherpad () { openEtherpad() {
this.etherpad = new Etherpad(this.domain, this.name); this.etherpad = new Etherpad(this.domain, this.name);
VideoLayout.addLargeVideoContainer( VideoLayout.addLargeVideoContainer(
ETHERPAD_CONTAINER_TYPE, ETHERPAD_CONTAINER_TYPE,
@ -185,12 +223,12 @@ export default class EtherpadManager {
* Toggle Etherpad frame visibility. * Toggle Etherpad frame visibility.
* Open new Etherpad frame if there is no Etherpad frame yet. * Open new Etherpad frame if there is no Etherpad frame yet.
*/ */
toggleEtherpad () { toggleEtherpad() {
if (!this.isOpen) { if (!this.isOpen) {
this.openEtherpad(); this.openEtherpad();
} }
let isVisible = this.isVisible(); const isVisible = this.isVisible();
VideoLayout.showLargeVideoContainer( VideoLayout.showLargeVideoContainer(
ETHERPAD_CONTAINER_TYPE, !isVisible); ETHERPAD_CONTAINER_TYPE, !isVisible);

View File

@ -14,9 +14,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
const logger = require("jitsi-meet-logger").getLogger(__filename); const logger = require('jitsi-meet-logger').getLogger(__filename);
import UIEvents from "../../../service/UI/UIEvents"; import UIEvents from '../../../service/UI/UIEvents';
import UIUtil from '../util/UIUtil'; import UIUtil from '../util/UIUtil';
import VideoLayout from '../videolayout/VideoLayout'; import VideoLayout from '../videolayout/VideoLayout';
@ -84,7 +84,7 @@ let dialog = null;
*/ */
function _isRecordingButtonEnabled() { function _isRecordingButtonEnabled() {
return ( return (
interfaceConfig.TOOLBAR_BUTTONS.indexOf("recording") !== -1 interfaceConfig.TOOLBAR_BUTTONS.indexOf('recording') !== -1
&& config.enableRecording && config.enableRecording
&& APP.conference.isRecordingSupported()); && APP.conference.isRecordingSupported());
} }
@ -95,69 +95,75 @@ function _isRecordingButtonEnabled() {
*/ */
function _requestLiveStreamId() { function _requestLiveStreamId() {
const cancelButton const cancelButton
= APP.translation.generateTranslationHTML("dialog.Cancel"); = APP.translation.generateTranslationHTML('dialog.Cancel');
const backButton = APP.translation.generateTranslationHTML("dialog.Back"); const backButton = APP.translation.generateTranslationHTML('dialog.Back');
const startStreamingButton const startStreamingButton
= APP.translation.generateTranslationHTML("dialog.startLiveStreaming"); = APP.translation.generateTranslationHTML('dialog.startLiveStreaming');
const streamIdRequired const streamIdRequired
= APP.translation.generateTranslationHTML( = APP.translation.generateTranslationHTML(
"liveStreaming.streamIdRequired"); 'liveStreaming.streamIdRequired');
const streamIdHelp const streamIdHelp
= APP.translation.generateTranslationHTML( = APP.translation.generateTranslationHTML(
"liveStreaming.streamIdHelp"); 'liveStreaming.streamIdHelp');
return new Promise(function (resolve, reject) { return new Promise((resolve, reject) => {
dialog = APP.UI.messageHandler.openDialogWithStates({ dialog = APP.UI.messageHandler.openDialogWithStates({
state0: { state0: {
titleKey: "dialog.liveStreaming", titleKey: 'dialog.liveStreaming',
html: html:
`<input class="input-control" `<input class="input-control"
name="streamId" type="text" name="streamId" type="text"
data-i18n="[placeholder]dialog.streamKey" data-i18n="[placeholder]dialog.streamKey"
autofocus><div style="text-align: right"> autofocus><div style="text-align: right">
<a class="helper-link" target="_new" <a class="helper-link" target="_new"
href="${interfaceConfig.LIVE_STREAMING_HELP_LINK}">` href="${interfaceConfig.LIVE_STREAMING_HELP_LINK}">${
+ streamIdHelp streamIdHelp
+ `</a></div>`, }</a></div>`,
persistent: false, persistent: false,
buttons: [ buttons: [
{title: cancelButton, value: false}, { title: cancelButton,
{title: startStreamingButton, value: true} value: false },
{ title: startStreamingButton,
value: true }
], ],
focus: ':input:first', focus: ':input:first',
defaultButton: 1, defaultButton: 1,
submit: function (e, v, m, f) { submit(e, v, m, f) { // eslint-disable-line max-params
e.preventDefault(); e.preventDefault();
if (v) { if (v) {
if (f.streamId && f.streamId.length > 0) { if (f.streamId && f.streamId.length > 0) {
resolve(UIUtil.escapeHtml(f.streamId)); resolve(UIUtil.escapeHtml(f.streamId));
dialog.close(); dialog.close();
return; return;
} }
else { dialog.goToState('state1');
dialog.goToState('state1');
return false;
}
} else {
reject(APP.UI.messageHandler.CANCEL);
dialog.close();
return false; return false;
} }
reject(APP.UI.messageHandler.CANCEL);
dialog.close();
return false;
} }
}, },
state1: { state1: {
titleKey: "dialog.liveStreaming", titleKey: 'dialog.liveStreaming',
html: streamIdRequired, html: streamIdRequired,
persistent: false, persistent: false,
buttons: [ buttons: [
{title: cancelButton, value: false}, { title: cancelButton,
{title: backButton, value: true} value: false },
{ title: backButton,
value: true }
], ],
focus: ':input:first', focus: ':input:first',
defaultButton: 1, defaultButton: 1,
submit: function (e, v) { submit(e, v) {
e.preventDefault(); e.preventDefault();
if (v === 0) { if (v === 0) {
reject(APP.UI.messageHandler.CANCEL); reject(APP.UI.messageHandler.CANCEL);
@ -168,7 +174,7 @@ function _requestLiveStreamId() {
} }
} }
}, { }, {
close: function () { close() {
dialog = null; dialog = null;
} }
}); });
@ -180,26 +186,29 @@ function _requestLiveStreamId() {
* @returns {Promise} * @returns {Promise}
*/ */
function _requestRecordingToken() { function _requestRecordingToken() {
let titleKey = "dialog.recordingToken"; const titleKey = 'dialog.recordingToken';
let msgString = ( const msgString
`<input name="recordingToken" type="text" = `<input name="recordingToken" type="text"
data-i18n="[placeholder]dialog.token" data-i18n="[placeholder]dialog.token"
class="input-control" class="input-control"
autofocus>` autofocus>`
);
return new Promise(function (resolve, reject) { ;
return new Promise((resolve, reject) => {
dialog = APP.UI.messageHandler.openTwoButtonDialog({ dialog = APP.UI.messageHandler.openTwoButtonDialog({
titleKey, titleKey,
msgString, msgString,
leftButtonKey: 'dialog.Save', leftButtonKey: 'dialog.Save',
submitFunction: function (e, v, m, f) { submitFunction(e, v, m, f) { // eslint-disable-line max-params
if (v && f.recordingToken) { if (v && f.recordingToken) {
resolve(UIUtil.escapeHtml(f.recordingToken)); resolve(UIUtil.escapeHtml(f.recordingToken));
} else { } else {
reject(APP.UI.messageHandler.CANCEL); reject(APP.UI.messageHandler.CANCEL);
} }
}, },
closeFunction: function () { closeFunction() {
dialog = null; dialog = null;
}, },
focus: ':input:first' focus: ':input:first'
@ -215,18 +224,18 @@ function _requestRecordingToken() {
* @private * @private
*/ */
function _showStopRecordingPrompt(recordingType) { function _showStopRecordingPrompt(recordingType) {
var title; let title;
var message; let message;
var buttonKey; let buttonKey;
if (recordingType === "jibri") {
title = "dialog.liveStreaming"; if (recordingType === 'jibri') {
message = "dialog.stopStreamingWarning"; title = 'dialog.liveStreaming';
buttonKey = "dialog.stopLiveStreaming"; message = 'dialog.stopStreamingWarning';
} buttonKey = 'dialog.stopLiveStreaming';
else { } else {
title = "dialog.recording"; title = 'dialog.recording';
message = "dialog.stopRecordingWarning"; message = 'dialog.stopRecordingWarning';
buttonKey = "dialog.stopRecording"; buttonKey = 'dialog.stopRecording';
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -248,7 +257,10 @@ function _showStopRecordingPrompt(recordingType) {
* @returns {boolean} true if the condition is met or false otherwise. * @returns {boolean} true if the condition is met or false otherwise.
*/ */
function isStartingStatus(status) { function isStartingStatus(status) {
return status === JitsiRecordingStatus.PENDING || status === JitsiRecordingStatus.RETRYING; return (
status === JitsiRecordingStatus.PENDING
|| status === JitsiRecordingStatus.RETRYING
);
} }
/** /**
@ -256,7 +268,7 @@ function isStartingStatus(status) {
* @type {{init, initRecordingButton, showRecordingButton, updateRecordingState, * @type {{init, initRecordingButton, showRecordingButton, updateRecordingState,
* updateRecordingUI, checkAutoRecord}} * updateRecordingUI, checkAutoRecord}}
*/ */
var Recording = { const Recording = {
/** /**
* Initializes the recording UI. * Initializes the recording UI.
*/ */
@ -267,11 +279,10 @@ var Recording = {
this.updateRecordingState(APP.conference.getRecordingState()); this.updateRecordingState(APP.conference.getRecordingState());
if (recordingType === 'jibri') { if (recordingType === 'jibri') {
this.baseClass = "fa fa-play-circle"; this.baseClass = 'fa fa-play-circle';
Object.assign(this, STREAMING_TRANSLATION_KEYS); Object.assign(this, STREAMING_TRANSLATION_KEYS);
} } else {
else { this.baseClass = 'icon-recEnable';
this.baseClass = "icon-recEnable";
Object.assign(this, RECORDING_TRANSLATION_KEYS); Object.assign(this, RECORDING_TRANSLATION_KEYS);
} }
@ -301,7 +312,7 @@ var Recording = {
const selector = $('#toolbar_button_record'); const selector = $('#toolbar_button_record');
selector.addClass(this.baseClass); selector.addClass(this.baseClass);
selector.attr("data-i18n", "[content]" + this.recordingButtonTooltip); selector.attr('data-i18n', `[content]${this.recordingButtonTooltip}`);
APP.translation.translateElement(selector); APP.translation.translateElement(selector);
}, },
@ -310,8 +321,8 @@ var Recording = {
* @param show {true} to show the recording button, {false} to hide it * @param show {true} to show the recording button, {false} to hide it
*/ */
showRecordingButton(show) { showRecordingButton(show) {
let shouldShow = show && _isRecordingButtonEnabled(); const shouldShow = show && _isRecordingButtonEnabled();
let id = 'toolbar_button_record'; const id = 'toolbar_button_record';
UIUtil.setVisible(id, shouldShow); UIUtil.setVisible(id, shouldShow);
}, },
@ -322,12 +333,14 @@ var Recording = {
*/ */
updateRecordingState(recordingState) { updateRecordingState(recordingState) {
// I'm the recorder, so I don't want to see any UI related to states. // I'm the recorder, so I don't want to see any UI related to states.
if (config.iAmRecorder) if (config.iAmRecorder) {
return; return;
}
// If there's no state change, we ignore the update. // If there's no state change, we ignore the update.
if (!recordingState || this.currentState === recordingState) if (!recordingState || this.currentState === recordingState) {
return; return;
}
this.updateRecordingUI(recordingState); this.updateRecordingUI(recordingState);
}, },
@ -338,7 +351,8 @@ var Recording = {
*/ */
updateRecordingUI(recordingState) { updateRecordingUI(recordingState) {
let oldState = this.currentState; const oldState = this.currentState;
this.currentState = recordingState; this.currentState = recordingState;
let labelDisplayConfiguration; let labelDisplayConfiguration;
@ -366,6 +380,7 @@ var Recording = {
// We don't want UI changes if this is an availability change. // We don't want UI changes if this is an availability change.
if (oldState !== JitsiRecordingStatus.ON && !wasInStartingStatus) { if (oldState !== JitsiRecordingStatus.ON && !wasInStartingStatus) {
APP.store.dispatch(updateRecordingState({ recordingState })); APP.store.dispatch(updateRecordingState({ recordingState }));
return; return;
} }
@ -378,7 +393,7 @@ var Recording = {
this._setToolbarButtonToggled(false); this._setToolbarButtonToggled(false);
setTimeout(function(){ setTimeout(() => {
APP.store.dispatch(hideRecordingLabel()); APP.store.dispatch(hideRecordingLabel());
}, 5000); }, 5000);
@ -408,7 +423,8 @@ var Recording = {
} }
// Return an empty label display configuration to indicate no label // Return an empty label display configuration to indicate no label
// should be displayed. The JitsiRecordingStatus.AVAIABLE case is handled here. // should be displayed. The JitsiRecordingStatus.AVAIABLE case is
// handled here.
default: { default: {
labelDisplayConfiguration = null; labelDisplayConfiguration = null;
} }
@ -450,42 +466,48 @@ var Recording = {
this.eventEmitter.emit(UIEvents.RECORDING_TOGGLED); this.eventEmitter.emit(UIEvents.RECORDING_TOGGLED);
sendEvent('recording.stopped'); sendEvent('recording.stopped');
}, },
() => {}); () => {}); // eslint-disable-line no-empty-function
break; break;
} }
case JitsiRecordingStatus.AVAILABLE: case JitsiRecordingStatus.AVAILABLE:
case JitsiRecordingStatus.OFF: { case JitsiRecordingStatus.OFF: {
if (this.recordingType === 'jibri') if (this.recordingType === 'jibri') {
_requestLiveStreamId().then(streamId => { _requestLiveStreamId()
.then(streamId => {
this.eventEmitter.emit( this.eventEmitter.emit(
UIEvents.RECORDING_TOGGLED, UIEvents.RECORDING_TOGGLED,
{ streamId }); { streamId });
sendEvent('recording.started'); sendEvent('recording.started');
}).catch(reason => { })
if (reason !== APP.UI.messageHandler.CANCEL) .catch(reason => {
logger.error(reason); if (reason === APP.UI.messageHandler.CANCEL) {
else
sendEvent('recording.canceled'); sendEvent('recording.canceled');
} else {
logger.error(reason);
}
}); });
else { } else {
if (this.predefinedToken) { if (this.predefinedToken) {
this.eventEmitter.emit( this.eventEmitter.emit(
UIEvents.RECORDING_TOGGLED, UIEvents.RECORDING_TOGGLED,
{ token: this.predefinedToken }); { token: this.predefinedToken });
sendEvent('recording.started'); sendEvent('recording.started');
return; return;
} }
_requestRecordingToken().then((token) => { _requestRecordingToken().then(token => {
this.eventEmitter.emit( this.eventEmitter.emit(
UIEvents.RECORDING_TOGGLED, UIEvents.RECORDING_TOGGLED,
{ token }); { token });
sendEvent('recording.started'); sendEvent('recording.started');
}).catch(reason => { })
if (reason !== APP.UI.messageHandler.CANCEL) .catch(reason => {
logger.error(reason); if (reason === APP.UI.messageHandler.CANCEL) {
else
sendEvent('recording.canceled'); sendEvent('recording.canceled');
} else {
logger.error(reason);
}
}); });
} }
break; break;
@ -521,7 +543,7 @@ var Recording = {
* or not * or not
*/ */
_setToolbarButtonToggled(isToggled) { _setToolbarButtonToggled(isToggled) {
$("#toolbar_button_record").toggleClass("toggled", isToggled); $('#toolbar_button_record').toggleClass('toggled', isToggled);
} }
}; };

View File

@ -1,10 +1,10 @@
/* global $, APP, YT, onPlayerReady, onPlayerStateChange, onPlayerError */ /* global $, APP, YT, onPlayerReady, onPlayerStateChange, onPlayerError */
const logger = require("jitsi-meet-logger").getLogger(__filename); const logger = require('jitsi-meet-logger').getLogger(__filename);
import UIUtil from '../util/UIUtil'; import UIUtil from '../util/UIUtil';
import UIEvents from '../../../service/UI/UIEvents'; import UIEvents from '../../../service/UI/UIEvents';
import VideoLayout from "../videolayout/VideoLayout"; import VideoLayout from '../videolayout/VideoLayout';
import LargeContainer from '../videolayout/LargeContainer'; import LargeContainer from '../videolayout/LargeContainer';
import Filmstrip from '../videolayout/Filmstrip'; import Filmstrip from '../videolayout/Filmstrip';
@ -17,13 +17,13 @@ import { dockToolbox, showToolbox } from '../../../react/features/toolbox';
import SharedVideoThumb from './SharedVideoThumb'; import SharedVideoThumb from './SharedVideoThumb';
export const SHARED_VIDEO_CONTAINER_TYPE = "sharedvideo"; export const SHARED_VIDEO_CONTAINER_TYPE = 'sharedvideo';
/** /**
* Example shared video link. * Example shared video link.
* @type {string} * @type {string}
*/ */
const defaultSharedVideoLink = "https://www.youtube.com/watch?v=xNXN7CZk8X0"; const defaultSharedVideoLink = 'https://www.youtube.com/watch?v=xNXN7CZk8X0';
const updateInterval = 5000; // milliseconds const updateInterval = 5000; // milliseconds
/** /**
@ -36,7 +36,10 @@ let dialog = null;
* Manager of shared video. * Manager of shared video.
*/ */
export default class SharedVideoManager { export default class SharedVideoManager {
constructor (emitter) { /**
*
*/
constructor(emitter) {
this.emitter = emitter; this.emitter = emitter;
this.isSharedVideoShown = false; this.isSharedVideoShown = false;
this.isPlayerAPILoaded = false; this.isPlayerAPILoaded = false;
@ -52,10 +55,10 @@ export default class SharedVideoManager {
* currently on. * currently on.
*/ */
isSharedVideoVolumeOn() { isSharedVideoVolumeOn() {
return (this.player return this.player
&& this.player.getPlayerState() === YT.PlayerState.PLAYING && this.player.getPlayerState() === YT.PlayerState.PLAYING
&& !this.player.isMuted() && !this.player.isMuted()
&& this.player.getVolume() > 0); && this.player.getVolume() > 0;
} }
/** /**
@ -70,11 +73,12 @@ export default class SharedVideoManager {
* Starts shared video by asking user for url, or if its already working * Starts shared video by asking user for url, or if its already working
* asks whether the user wants to stop sharing the video. * asks whether the user wants to stop sharing the video.
*/ */
toggleSharedVideo () { toggleSharedVideo() {
if (dialog) if (dialog) {
return; return;
}
if(!this.isSharedVideoShown) { if (!this.isSharedVideoShown) {
requestVideoLink().then( requestVideoLink().then(
url => { url => {
this.emitter.emit( this.emitter.emit(
@ -86,10 +90,11 @@ export default class SharedVideoManager {
sendEvent('sharedvideo.canceled'); sendEvent('sharedvideo.canceled');
} }
); );
return; return;
} }
if(APP.conference.isLocalId(this.from)) { if (APP.conference.isLocalId(this.from)) {
showStopVideoPropmpt().then( showStopVideoPropmpt().then(
() => { () => {
// make sure we stop updates for playing before we send stop // make sure we stop updates for playing before we send stop
@ -104,13 +109,13 @@ export default class SharedVideoManager {
UIEvents.UPDATE_SHARED_VIDEO, this.url, 'stop'); UIEvents.UPDATE_SHARED_VIDEO, this.url, 'stop');
sendEvent('sharedvideo.stoped'); sendEvent('sharedvideo.stoped');
}, },
() => {}); () => {}); // eslint-disable-line no-empty-function
} else { } else {
dialog = APP.UI.messageHandler.openMessageDialog( dialog = APP.UI.messageHandler.openMessageDialog(
"dialog.shareVideoTitle", 'dialog.shareVideoTitle',
"dialog.alreadySharedVideoMsg", 'dialog.alreadySharedVideoMsg',
null, null,
function () { () => {
dialog = null; dialog = null;
} }
); );
@ -126,9 +131,10 @@ export default class SharedVideoManager {
* @param url the video url * @param url the video url
* @param attributes * @param attributes
*/ */
onSharedVideoStart (id, url, attributes) { onSharedVideoStart(id, url, attributes) {
if (this.isSharedVideoShown) if (this.isSharedVideoShown) {
return; return;
}
this.isSharedVideoShown = true; this.isSharedVideoShown = true;
@ -140,15 +146,16 @@ export default class SharedVideoManager {
this.mutedWithUserInteraction = APP.conference.isLocalAudioMuted(); this.mutedWithUserInteraction = APP.conference.isLocalAudioMuted();
//listen for local audio mute events // listen for local audio mute events
this.localAudioMutedListener = this.onLocalAudioMuted.bind(this); this.localAudioMutedListener = this.onLocalAudioMuted.bind(this);
this.emitter.on(UIEvents.AUDIO_MUTED, this.localAudioMutedListener); this.emitter.on(UIEvents.AUDIO_MUTED, this.localAudioMutedListener);
// This code loads the IFrame Player API code asynchronously. // This code loads the IFrame Player API code asynchronously.
var tag = document.createElement('script'); const tag = document.createElement('script');
tag.src = 'https://www.youtube.com/iframe_api';
const firstScriptTag = document.getElementsByTagName('script')[0];
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// sometimes we receive errors like player not defined // sometimes we receive errors like player not defined
@ -158,14 +165,16 @@ export default class SharedVideoManager {
// and will process any initial attributes if any // and will process any initial attributes if any
this.initialAttributes = attributes; this.initialAttributes = attributes;
var self = this; const self = this;
if(self.isPlayerAPILoaded)
if (self.isPlayerAPILoaded) {
window.onYouTubeIframeAPIReady(); window.onYouTubeIframeAPIReady();
else } else {
window.onYouTubeIframeAPIReady = function() { window.onYouTubeIframeAPIReady = function() {
self.isPlayerAPILoaded = true; self.isPlayerAPILoaded = true;
let showControls = APP.conference.isLocalId(self.from) ? 1 : 0; const showControls
let p = new YT.Player('sharedVideoIFrame', { = APP.conference.isLocalId(self.from) ? 1 : 0;
const p = new YT.Player('sharedVideoIFrame', {
height: '100%', height: '100%',
width: '100%', width: '100%',
videoId: self.url, videoId: self.url,
@ -174,7 +183,7 @@ export default class SharedVideoManager {
'fs': '0', 'fs': '0',
'autoplay': 0, 'autoplay': 0,
'controls': showControls, 'controls': showControls,
'rel' : 0 'rel': 0
}, },
events: { events: {
'onReady': onPlayerReady, 'onReady': onPlayerReady,
@ -185,28 +194,28 @@ export default class SharedVideoManager {
// add listener for volume changes // add listener for volume changes
p.addEventListener( p.addEventListener(
"onVolumeChange", "onVolumeChange"); 'onVolumeChange', 'onVolumeChange');
if (APP.conference.isLocalId(self.from)){ if (APP.conference.isLocalId(self.from)) {
// adds progress listener that will be firing events // adds progress listener that will be firing events
// while we are paused and we change the progress of the // while we are paused and we change the progress of the
// video (seeking forward or backward on the video) // video (seeking forward or backward on the video)
p.addEventListener( p.addEventListener(
"onVideoProgress", "onVideoProgress"); 'onVideoProgress', 'onVideoProgress');
} }
}; };
}
/** /**
* Indicates that a change in state has occurred for the shared video. * Indicates that a change in state has occurred for the shared video.
* @param event the event notifying us of the change * @param event the event notifying us of the change
*/ */
window.onPlayerStateChange = function(event) { window.onPlayerStateChange = function(event) {
// eslint-disable-next-line eqeqeq
if (event.data == YT.PlayerState.PLAYING) { if (event.data == YT.PlayerState.PLAYING) {
self.player = event.target; self.player = event.target;
if(self.initialAttributes) if (self.initialAttributes) {
{
// If a network update has occurred already now is the // If a network update has occurred already now is the
// time to process it. // time to process it.
self.processVideoUpdate( self.processVideoUpdate(
@ -216,10 +225,12 @@ export default class SharedVideoManager {
self.initialAttributes = null; self.initialAttributes = null;
} }
self.smartAudioMute(); self.smartAudioMute();
// eslint-disable-next-line eqeqeq
} else if (event.data == YT.PlayerState.PAUSED) { } else if (event.data == YT.PlayerState.PAUSED) {
self.smartAudioUnmute(); self.smartAudioUnmute();
sendEvent('sharedvideo.paused'); sendEvent('sharedvideo.paused');
} }
// eslint-disable-next-line eqeqeq
self.fireSharedVideoEvent(event.data == YT.PlayerState.PAUSED); self.fireSharedVideoEvent(event.data == YT.PlayerState.PAUSED);
}; };
@ -227,8 +238,10 @@ export default class SharedVideoManager {
* Track player progress while paused. * Track player progress while paused.
* @param event * @param event
*/ */
window.onVideoProgress = function (event) { window.onVideoProgress = function(event) {
let state = event.target.getPlayerState(); const state = event.target.getPlayerState();
// eslint-disable-next-line eqeqeq
if (state == YT.PlayerState.PAUSED) { if (state == YT.PlayerState.PAUSED) {
self.fireSharedVideoEvent(true); self.fireSharedVideoEvent(true);
} }
@ -238,38 +251,44 @@ export default class SharedVideoManager {
* Gets notified for volume state changed. * Gets notified for volume state changed.
* @param event * @param event
*/ */
window.onVolumeChange = function (event) { window.onVolumeChange = function(event) {
self.fireSharedVideoEvent(); self.fireSharedVideoEvent();
// let's check, if player is not muted lets mute locally // let's check, if player is not muted lets mute locally
if(event.data.volume > 0 && !event.data.muted) { if (event.data.volume > 0 && !event.data.muted) {
self.smartAudioMute(); self.smartAudioMute();
} } else if (event.data.volume <= 0 || event.data.muted) {
else if (event.data.volume <=0 || event.data.muted) {
self.smartAudioUnmute(); self.smartAudioUnmute();
} }
sendEvent('sharedvideo.volumechanged'); sendEvent('sharedvideo.volumechanged');
}; };
window.onPlayerReady = function(event) { window.onPlayerReady = function(event) {
let player = event.target; const player = event.target;
// do not relay on autoplay as it is not sending all of the events // do not relay on autoplay as it is not sending all of the events
// in onPlayerStateChange // in onPlayerStateChange
player.playVideo(); player.playVideo();
let thumb = new SharedVideoThumb( const thumb = new SharedVideoThumb(
self.url, SHARED_VIDEO_CONTAINER_TYPE, VideoLayout); self.url, SHARED_VIDEO_CONTAINER_TYPE, VideoLayout);
thumb.setDisplayName(player.getVideoData().title); thumb.setDisplayName(player.getVideoData().title);
VideoLayout.addRemoteVideoContainer(self.url, thumb); VideoLayout.addRemoteVideoContainer(self.url, thumb);
let iframe = player.getIframe(); const iframe = player.getIframe();
self.sharedVideo = new SharedVideoContainer(
{url, iframe, player});
//prevents pausing participants not sharing the video // eslint-disable-next-line no-use-before-define
self.sharedVideo = new SharedVideoContainer(
{ url,
iframe,
player });
// prevents pausing participants not sharing the video
// to pause the video // to pause the video
if (!APP.conference.isLocalId(self.from)) { if (!APP.conference.isLocalId(self.from)) {
$("#sharedVideo").css("pointer-events","none"); $('#sharedVideo').css('pointer-events', 'none');
} }
VideoLayout.addLargeVideoContainer( VideoLayout.addLargeVideoContainer(
@ -285,7 +304,7 @@ export default class SharedVideoManager {
// If we are sending the command and we are starting the player // If we are sending the command and we are starting the player
// we need to continuously send the player current time position // we need to continuously send the player current time position
if(APP.conference.isLocalId(self.from)) { if (APP.conference.isLocalId(self.from)) {
self.intervalId = setInterval( self.intervalId = setInterval(
self.fireSharedVideoEvent.bind(self), self.fireSharedVideoEvent.bind(self),
updateInterval); updateInterval);
@ -293,7 +312,8 @@ export default class SharedVideoManager {
}; };
window.onPlayerError = function(event) { window.onPlayerError = function(event) {
logger.error("Error in the player:", event.data); logger.error('Error in the player:', event.data);
// store the error player, so we can remove it // store the error player, so we can remove it
self.errorInPlayer = event.target; self.errorInPlayer = event.target;
}; };
@ -304,21 +324,23 @@ export default class SharedVideoManager {
* @param player the player to operate over * @param player the player to operate over
* @param attributes the attributes with the player state we want * @param attributes the attributes with the player state we want
*/ */
processVideoUpdate (player, attributes) processVideoUpdate(player, attributes) {
{ if (!attributes) {
if(!attributes)
return; return;
}
// eslint-disable-next-line eqeqeq
if (attributes.state == 'playing') { if (attributes.state == 'playing') {
let isPlayerPaused const isPlayerPaused
= (this.player.getPlayerState() === YT.PlayerState.PAUSED); = this.player.getPlayerState() === YT.PlayerState.PAUSED;
// If our player is currently paused force the seek. // If our player is currently paused force the seek.
this.processTime(player, attributes, isPlayerPaused); this.processTime(player, attributes, isPlayerPaused);
// Process mute. // Process mute.
let isAttrMuted = (attributes.muted === "true"); const isAttrMuted = attributes.muted === 'true';
if (player.isMuted() !== isAttrMuted) { if (player.isMuted() !== isAttrMuted) {
this.smartPlayerMute(isAttrMuted, true); this.smartPlayerMute(isAttrMuted, true);
} }
@ -326,16 +348,18 @@ export default class SharedVideoManager {
// Process volume // Process volume
if (!isAttrMuted if (!isAttrMuted
&& attributes.volume !== undefined && attributes.volume !== undefined
// eslint-disable-next-line eqeqeq
&& player.getVolume() != attributes.volume) { && player.getVolume() != attributes.volume) {
player.setVolume(attributes.volume); player.setVolume(attributes.volume);
logger.info("Player change of volume:" + attributes.volume); logger.info(`Player change of volume:${attributes.volume}`);
this.showSharedVideoMutedPopup(false); this.showSharedVideoMutedPopup(false);
} }
if (isPlayerPaused) if (isPlayerPaused) {
player.playVideo(); player.playVideo();
}
// eslint-disable-next-line eqeqeq
} else if (attributes.state == 'pause') { } else if (attributes.state == 'pause') {
// if its not paused, pause it // if its not paused, pause it
player.pauseVideo(); player.pauseVideo();
@ -350,23 +374,23 @@ export default class SharedVideoManager {
* @param attributes the attributes with the player state we want * @param attributes the attributes with the player state we want
* @param forceSeek whether seek should be forced * @param forceSeek whether seek should be forced
*/ */
processTime (player, attributes, forceSeek) processTime(player, attributes, forceSeek) {
{ if (forceSeek) {
if(forceSeek) { logger.info('Player seekTo:', attributes.time);
logger.info("Player seekTo:", attributes.time);
player.seekTo(attributes.time); player.seekTo(attributes.time);
return; return;
} }
// check received time and current time // check received time and current time
let currentPosition = player.getCurrentTime(); const currentPosition = player.getCurrentTime();
let diff = Math.abs(attributes.time - currentPosition); const diff = Math.abs(attributes.time - currentPosition);
// if we drift more than the interval for checking // if we drift more than the interval for checking
// sync, the interval is in milliseconds // sync, the interval is in milliseconds
if(diff > updateInterval/1000) { if (diff > updateInterval / 1000) {
logger.info("Player seekTo:", attributes.time, logger.info('Player seekTo:', attributes.time,
" current time is:", currentPosition, " diff:", diff); ' current time is:', currentPosition, ' diff:', diff);
player.seekTo(attributes.time); player.seekTo(attributes.time);
} }
} }
@ -374,24 +398,25 @@ export default class SharedVideoManager {
/** /**
* Checks current state of the player and fire an event with the values. * Checks current state of the player and fire an event with the values.
*/ */
fireSharedVideoEvent(sendPauseEvent) fireSharedVideoEvent(sendPauseEvent) {
{
// ignore update checks if we are not the owner of the video // ignore update checks if we are not the owner of the video
// or there is still no player defined or we are stopped // or there is still no player defined or we are stopped
// (in a process of stopping) // (in a process of stopping)
if(!APP.conference.isLocalId(this.from) || !this.player if (!APP.conference.isLocalId(this.from) || !this.player
|| !this.isSharedVideoShown) || !this.isSharedVideoShown) {
return; return;
}
const state = this.player.getPlayerState();
let state = this.player.getPlayerState();
// if its paused and haven't been pause - send paused // if its paused and haven't been pause - send paused
if (state === YT.PlayerState.PAUSED && sendPauseEvent) { if (state === YT.PlayerState.PAUSED && sendPauseEvent) {
this.emitter.emit(UIEvents.UPDATE_SHARED_VIDEO, this.emitter.emit(UIEvents.UPDATE_SHARED_VIDEO,
this.url, 'pause', this.player.getCurrentTime()); this.url, 'pause', this.player.getCurrentTime());
} } else if (state === YT.PlayerState.PLAYING) {
// if its playing and it was paused - send update with time // if its playing and it was paused - send update with time
// if its playing and was playing just send update with time // if its playing and was playing just send update with time
else if (state === YT.PlayerState.PLAYING) {
this.emitter.emit(UIEvents.UPDATE_SHARED_VIDEO, this.emitter.emit(UIEvents.UPDATE_SHARED_VIDEO,
this.url, 'playing', this.url, 'playing',
this.player.getCurrentTime(), this.player.getCurrentTime(),
@ -407,20 +432,22 @@ export default class SharedVideoManager {
* @param url the video url * @param url the video url
* @param attributes * @param attributes
*/ */
onSharedVideoUpdate (id, url, attributes) { onSharedVideoUpdate(id, url, attributes) {
// if we are sending the event ignore // if we are sending the event ignore
if(APP.conference.isLocalId(this.from)) { if (APP.conference.isLocalId(this.from)) {
return; return;
} }
if(!this.isSharedVideoShown) { if (!this.isSharedVideoShown) {
this.onSharedVideoStart(id, url, attributes); this.onSharedVideoStart(id, url, attributes);
return; return;
} }
if(!this.player) // eslint-disable-next-line no-negated-condition
if (!this.player) {
this.initialAttributes = attributes; this.initialAttributes = attributes;
else { } else {
this.processVideoUpdate(this.player, attributes); this.processVideoUpdate(this.player, attributes);
} }
} }
@ -431,18 +458,21 @@ export default class SharedVideoManager {
* left and we want to remove video if the user sharing it left). * left and we want to remove video if the user sharing it left).
* @param id the id of the sender of the command * @param id the id of the sender of the command
*/ */
onSharedVideoStop (id, attributes) { onSharedVideoStop(id, attributes) {
if (!this.isSharedVideoShown) if (!this.isSharedVideoShown) {
return; return;
}
if(this.from !== id) if (this.from !== id) {
return; return;
}
if(!this.player) { if (!this.player) {
// if there is no error in the player till now, // if there is no error in the player till now,
// store the initial attributes // store the initial attributes
if (!this.errorInPlayer) { if (!this.errorInPlayer) {
this.initialAttributes = attributes; this.initialAttributes = attributes;
return; return;
} }
} }
@ -458,18 +488,19 @@ export default class SharedVideoManager {
VideoLayout.removeLargeVideoContainer( VideoLayout.removeLargeVideoContainer(
SHARED_VIDEO_CONTAINER_TYPE); SHARED_VIDEO_CONTAINER_TYPE);
if(this.player) { if (this.player) {
this.player.destroy(); this.player.destroy();
this.player = null; this.player = null;
} // if there is an error in player, remove that instance } else if (this.errorInPlayer) {
else if (this.errorInPlayer) { // if there is an error in player, remove that instance
this.errorInPlayer.destroy(); this.errorInPlayer.destroy();
this.errorInPlayer = null; this.errorInPlayer = null;
} }
this.smartAudioUnmute(); this.smartAudioUnmute();
// revert to original behavior (prevents pausing // revert to original behavior (prevents pausing
// for participants not sharing the video to pause it) // for participants not sharing the video to pause it)
$("#sharedVideo").css("pointer-events","auto"); $('#sharedVideo').css('pointer-events', 'auto');
this.emitter.emit( this.emitter.emit(
UIEvents.UPDATE_SHARED_VIDEO, null, 'removed'); UIEvents.UPDATE_SHARED_VIDEO, null, 'removed');
@ -488,15 +519,16 @@ export default class SharedVideoManager {
* @param {boolean} indicates if this mute was a result of user interaction, * @param {boolean} indicates if this mute was a result of user interaction,
* i.e. pressing the mute button or it was programatically triggerred * i.e. pressing the mute button or it was programatically triggerred
*/ */
onLocalAudioMuted (muted, userInteraction) { onLocalAudioMuted(muted, userInteraction) {
if(!this.player) if (!this.player) {
return; return;
}
if (muted) { if (muted) {
this.mutedWithUserInteraction = userInteraction; this.mutedWithUserInteraction = userInteraction;
} } else if (this.player.getPlayerState() !== YT.PlayerState.PAUSED) {
else if (this.player.getPlayerState() !== YT.PlayerState.PAUSED) {
this.smartPlayerMute(true, false); this.smartPlayerMute(true, false);
// Check if we need to update other participants // Check if we need to update other participants
this.fireSharedVideoEvent(); this.fireSharedVideoEvent();
} }
@ -512,13 +544,14 @@ export default class SharedVideoManager {
if (!this.player.isMuted() && mute) { if (!this.player.isMuted() && mute) {
this.player.mute(); this.player.mute();
if (isVideoUpdate) if (isVideoUpdate) {
this.smartAudioUnmute(); this.smartAudioUnmute();
} }
else if (this.player.isMuted() && !mute) { } else if (this.player.isMuted() && !mute) {
this.player.unMute(); this.player.unMute();
if (isVideoUpdate) if (isVideoUpdate) {
this.smartAudioMute(); this.smartAudioMute();
}
} }
this.showSharedVideoMutedPopup(mute); this.showSharedVideoMutedPopup(mute);
@ -533,7 +566,7 @@ export default class SharedVideoManager {
if (APP.conference.isLocalAudioMuted() if (APP.conference.isLocalAudioMuted()
&& !this.mutedWithUserInteraction && !this.mutedWithUserInteraction
&& !this.isSharedVideoVolumeOn()) { && !this.isSharedVideoVolumeOn()) {
sendEvent("sharedvideo.audio.unmuted"); sendEvent('sharedvideo.audio.unmuted');
logger.log('Shared video: audio unmuted'); logger.log('Shared video: audio unmuted');
this.emitter.emit(UIEvents.AUDIO_MUTED, false, false); this.emitter.emit(UIEvents.AUDIO_MUTED, false, false);
this.showMicMutedPopup(false); this.showMicMutedPopup(false);
@ -547,7 +580,7 @@ export default class SharedVideoManager {
smartAudioMute() { smartAudioMute() {
if (!APP.conference.isLocalAudioMuted() if (!APP.conference.isLocalAudioMuted()
&& this.isSharedVideoVolumeOn()) { && this.isSharedVideoVolumeOn()) {
sendEvent("sharedvideo.audio.muted"); sendEvent('sharedvideo.audio.muted');
logger.log('Shared video: audio muted'); logger.log('Shared video: audio muted');
this.emitter.emit(UIEvents.AUDIO_MUTED, true, false); this.emitter.emit(UIEvents.AUDIO_MUTED, true, false);
this.showMicMutedPopup(true); this.showMicMutedPopup(true);
@ -559,9 +592,10 @@ export default class SharedVideoManager {
* of automatic mute after a shared video has started. * of automatic mute after a shared video has started.
* @param show boolean, show or hide the notification * @param show boolean, show or hide the notification
*/ */
showMicMutedPopup (show) { showMicMutedPopup(show) {
if(show) if (show) {
this.showSharedVideoMutedPopup(false); this.showSharedVideoMutedPopup(false);
}
APP.UI.showCustomToolbarPopup( APP.UI.showCustomToolbarPopup(
'microphone', 'micMutedPopup', show, 5000); 'microphone', 'micMutedPopup', show, 5000);
@ -573,9 +607,10 @@ export default class SharedVideoManager {
* mic. * mic.
* @param show boolean, show or hide the notification * @param show boolean, show or hide the notification
*/ */
showSharedVideoMutedPopup (show) { showSharedVideoMutedPopup(show) {
if(show) if (show) {
this.showMicMutedPopup(false); this.showMicMutedPopup(false);
}
APP.UI.showCustomToolbarPopup( APP.UI.showCustomToolbarPopup(
'sharedvideo', 'sharedVideoMutedPopup', show, 5000); 'sharedvideo', 'sharedVideoMutedPopup', show, 5000);
@ -586,8 +621,10 @@ export default class SharedVideoManager {
* Container for shared video iframe. * Container for shared video iframe.
*/ */
class SharedVideoContainer extends LargeContainer { class SharedVideoContainer extends LargeContainer {
/**
constructor ({url, iframe, player}) { *
*/
constructor({ url, iframe, player }) {
super(); super();
this.$iframe = $(iframe); this.$iframe = $(iframe);
@ -595,43 +632,62 @@ class SharedVideoContainer extends LargeContainer {
this.player = player; this.player = player;
} }
show () { /**
let self = this; *
*/
show() {
const self = this;
return new Promise(resolve => { return new Promise(resolve => {
this.$iframe.fadeIn(300, () => { this.$iframe.fadeIn(300, () => {
self.bodyBackground = document.body.style.background; self.bodyBackground = document.body.style.background;
document.body.style.background = 'black'; document.body.style.background = 'black';
this.$iframe.css({opacity: 1}); this.$iframe.css({ opacity: 1 });
APP.store.dispatch(dockToolbox(true)); APP.store.dispatch(dockToolbox(true));
resolve(); resolve();
}); });
}); });
} }
hide () { /**
let self = this; *
*/
hide() {
const self = this;
APP.store.dispatch(dockToolbox(false)); APP.store.dispatch(dockToolbox(false));
return new Promise(resolve => { return new Promise(resolve => {
this.$iframe.fadeOut(300, () => { this.$iframe.fadeOut(300, () => {
document.body.style.background = self.bodyBackground; document.body.style.background = self.bodyBackground;
this.$iframe.css({opacity: 0}); this.$iframe.css({ opacity: 0 });
resolve(); resolve();
}); });
}); });
} }
onHoverIn () { /**
*
*/
onHoverIn() {
APP.store.dispatch(showToolbox()); APP.store.dispatch(showToolbox());
} }
get id () { /**
*
*/
get id() {
return this.url; return this.url;
} }
resize (containerWidth, containerHeight) { /**
let height = containerHeight - Filmstrip.getFilmstripHeight(); *
*/
resize(containerWidth, containerHeight) {
const height = containerHeight - Filmstrip.getFilmstripHeight();
let width = containerWidth; const width = containerWidth;
this.$iframe.width(width).height(height); this.$iframe.width(width).height(height);
} }
@ -639,7 +695,7 @@ class SharedVideoContainer extends LargeContainer {
/** /**
* @return {boolean} do not switch on dominant speaker event if on stage. * @return {boolean} do not switch on dominant speaker event if on stage.
*/ */
stayOnStage () { stayOnStage() {
return false; return false;
} }
} }
@ -650,16 +706,18 @@ class SharedVideoContainer extends LargeContainer {
* @returns {boolean} * @returns {boolean}
*/ */
function getYoutubeLink(url) { function getYoutubeLink(url) {
let p = /^(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/;//jshint ignore:line const p = /^(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/;// eslint-disable-line max-len
return (url.match(p)) ? RegExp.$1 : false;
return url.match(p) ? RegExp.$1 : false;
} }
/** /**
* Ask user if he want to close shared video. * Ask user if he want to close shared video.
*/ */
function showStopVideoPropmpt() { function showStopVideoPropmpt() {
return new Promise(function (resolve, reject) { return new Promise((resolve, reject) => {
let submitFunction = function(e,v) { const submitFunction = function(e, v) {
if (v) { if (v) {
resolve(); resolve();
} else { } else {
@ -667,14 +725,14 @@ function showStopVideoPropmpt() {
} }
}; };
let closeFunction = function () { const closeFunction = function() {
dialog = null; dialog = null;
}; };
dialog = APP.UI.messageHandler.openTwoButtonDialog({ dialog = APP.UI.messageHandler.openTwoButtonDialog({
titleKey: "dialog.removeSharedVideoTitle", titleKey: 'dialog.removeSharedVideoTitle',
msgKey: "dialog.removeSharedVideoMsg", msgKey: 'dialog.removeSharedVideoMsg',
leftButtonKey: "dialog.Remove", leftButtonKey: 'dialog.Remove',
submitFunction, submitFunction,
closeFunction closeFunction
}); });
@ -686,46 +744,53 @@ function showStopVideoPropmpt() {
* Dialog validates client input to allow only youtube urls. * Dialog validates client input to allow only youtube urls.
*/ */
function requestVideoLink() { function requestVideoLink() {
let i18n = APP.translation; const i18n = APP.translation;
const cancelButton = i18n.generateTranslationHTML("dialog.Cancel"); const cancelButton = i18n.generateTranslationHTML('dialog.Cancel');
const shareButton = i18n.generateTranslationHTML("dialog.Share"); const shareButton = i18n.generateTranslationHTML('dialog.Share');
const backButton = i18n.generateTranslationHTML("dialog.Back"); const backButton = i18n.generateTranslationHTML('dialog.Back');
const linkError const linkError
= i18n.generateTranslationHTML("dialog.shareVideoLinkError"); = i18n.generateTranslationHTML('dialog.shareVideoLinkError');
return new Promise(function (resolve, reject) { return new Promise((resolve, reject) => {
dialog = APP.UI.messageHandler.openDialogWithStates({ dialog = APP.UI.messageHandler.openDialogWithStates({
state0: { state0: {
titleKey: "dialog.shareVideoTitle", titleKey: 'dialog.shareVideoTitle',
html: ` html: `
<input name="sharedVideoUrl" type="text" <input name='sharedVideoUrl' type='text'
class="input-control" class='input-control'
data-i18n="[placeholder]defaultLink" data-i18n='[placeholder]defaultLink'
autofocus>`, autofocus>`,
persistent: false, persistent: false,
buttons: [ buttons: [
{title: cancelButton, value: false}, { title: cancelButton,
{title: shareButton, value: true} value: false },
{ title: shareButton,
value: true }
], ],
focus: ':input:first', focus: ':input:first',
defaultButton: 1, defaultButton: 1,
submit: function (e, v, m, f) { submit(e, v, m, f) { // eslint-disable-line max-params
e.preventDefault(); e.preventDefault();
if (!v) { if (!v) {
reject('cancelled'); reject('cancelled');
dialog.close(); dialog.close();
return; return;
} }
let sharedVideoUrl = f.sharedVideoUrl; const sharedVideoUrl = f.sharedVideoUrl;
if (!sharedVideoUrl) { if (!sharedVideoUrl) {
return; return;
} }
let urlValue = encodeURI(UIUtil.escapeHtml(sharedVideoUrl)); const urlValue
let yVideoId = getYoutubeLink(urlValue); = encodeURI(UIUtil.escapeHtml(sharedVideoUrl));
const yVideoId = getYoutubeLink(urlValue);
if (!yVideoId) { if (!yVideoId) {
dialog.goToState('state1'); dialog.goToState('state1');
return false; return false;
} }
@ -735,16 +800,18 @@ function requestVideoLink() {
}, },
state1: { state1: {
titleKey: "dialog.shareVideoTitle", titleKey: 'dialog.shareVideoTitle',
html: linkError, html: linkError,
persistent: false, persistent: false,
buttons: [ buttons: [
{title: cancelButton, value: false}, { title: cancelButton,
{title: backButton, value: true} value: false },
{ title: backButton,
value: true }
], ],
focus: ':input:first', focus: ':input:first',
defaultButton: 1, defaultButton: 1,
submit: function (e, v) { submit(e, v) {
e.preventDefault(); e.preventDefault();
if (v === 0) { if (v === 0) {
reject(); reject();
@ -755,7 +822,7 @@ function requestVideoLink() {
} }
} }
}, { }, {
close: function () { close() {
dialog = null; dialog = null;
} }
}, { }, {

View File

@ -1,15 +1,17 @@
/* global $ */ /* global $ */
import SmallVideo from '../videolayout/SmallVideo'; import SmallVideo from '../videolayout/SmallVideo';
const logger = require("jitsi-meet-logger").getLogger(__filename); const logger = require('jitsi-meet-logger').getLogger(__filename);
export default function SharedVideoThumb (url, videoType, VideoLayout) /**
{ *
*/
export default function SharedVideoThumb(url, videoType, VideoLayout) {
this.id = url; this.id = url;
this.url = url; this.url = url;
this.setVideoType(videoType); this.setVideoType(videoType);
this.videoSpanId = "sharedVideoContainer"; this.videoSpanId = 'sharedVideoContainer';
this.container = this.createContainer(this.videoSpanId); this.container = this.createContainer(this.videoSpanId);
this.$container = $(this.container); this.$container = $(this.container);
this.container.onclick = this.videoClick.bind(this); this.container.onclick = this.videoClick.bind(this);
@ -23,42 +25,48 @@ SharedVideoThumb.prototype.constructor = SharedVideoThumb;
/** /**
* hide display name * hide display name
*/ */
// eslint-disable-next-line no-empty-function
SharedVideoThumb.prototype.setDeviceAvailabilityIcons = function() {};
SharedVideoThumb.prototype.setDeviceAvailabilityIcons = function () {}; // eslint-disable-next-line no-empty-function
SharedVideoThumb.prototype.avatarChanged = function() {};
SharedVideoThumb.prototype.avatarChanged = function () {}; SharedVideoThumb.prototype.createContainer = function(spanId) {
const container = document.createElement('span');
SharedVideoThumb.prototype.createContainer = function (spanId) {
var container = document.createElement('span');
container.id = spanId; container.id = spanId;
container.className = 'videocontainer'; container.className = 'videocontainer';
// add the avatar // add the avatar
var avatar = document.createElement('img'); const avatar = document.createElement('img');
avatar.className = 'sharedVideoAvatar'; avatar.className = 'sharedVideoAvatar';
avatar.src = "https://img.youtube.com/vi/" + this.url + "/0.jpg"; avatar.src = `https://img.youtube.com/vi/${this.url}/0.jpg`;
container.appendChild(avatar); container.appendChild(avatar);
const displayNameContainer = document.createElement('div'); const displayNameContainer = document.createElement('div');
displayNameContainer.className = 'displayNameContainer'; displayNameContainer.className = 'displayNameContainer';
container.appendChild(displayNameContainer); container.appendChild(displayNameContainer);
var remotes = document.getElementById('filmstripRemoteVideosContainer'); const remotes = document.getElementById('filmstripRemoteVideosContainer');
return remotes.appendChild(container); return remotes.appendChild(container);
}; };
/** /**
* The thumb click handler. * The thumb click handler.
*/ */
SharedVideoThumb.prototype.videoClick = function () { SharedVideoThumb.prototype.videoClick = function() {
this.VideoLayout.handleVideoThumbClicked(this.url); this.VideoLayout.handleVideoThumbClicked(this.url);
}; };
/** /**
* Removes RemoteVideo from the page. * Removes RemoteVideo from the page.
*/ */
SharedVideoThumb.prototype.remove = function () { SharedVideoThumb.prototype.remove = function() {
logger.log("Remove shared video thumb", this.id); logger.log('Remove shared video thumb', this.id);
// Make sure that the large video is updated if are removing its // Make sure that the large video is updated if are removing its
// corresponding small video. // corresponding small video.
@ -75,8 +83,9 @@ SharedVideoThumb.prototype.remove = function () {
*/ */
SharedVideoThumb.prototype.setDisplayName = function(displayName) { SharedVideoThumb.prototype.setDisplayName = function(displayName) {
if (!this.container) { if (!this.container) {
logger.warn( "Unable to set displayName - " + this.videoSpanId + logger.warn(`Unable to set displayName - ${this.videoSpanId
" does not exist"); } does not exist`);
return; return;
} }

View File

@ -1,5 +1,5 @@
/* global $ */ /* global $ */
import UIEvents from "../../../service/UI/UIEvents"; import UIEvents from '../../../service/UI/UIEvents';
/** /**
* Handles open and close of the extended toolbar side panel * Handles open and close of the extended toolbar side panel
@ -19,22 +19,28 @@ const SideContainerToggler = {
// We may not have a side toolbar container, for example, in // We may not have a side toolbar container, for example, in
// filmstrip-only mode. // filmstrip-only mode.
const sideToolbarContainer const sideToolbarContainer
= document.getElementById("sideToolbarContainer"); = document.getElementById('sideToolbarContainer');
if (!sideToolbarContainer) if (!sideToolbarContainer) {
return; return;
}
// Adds a listener for the animationend event that would take care of // Adds a listener for the animationend event that would take care of
// hiding all internal containers when the extendedToolbarPanel is // hiding all internal containers when the extendedToolbarPanel is
// closed. // closed.
sideToolbarContainer.addEventListener( sideToolbarContainer.addEventListener(
"animationend", 'animationend',
function(e) { e => {
if (e.animationName === "slideOutExt") if (e.animationName === 'slideOutExt') {
$("#sideToolbarContainer").children().each(function() { $('#sideToolbarContainer').children()
if ($(this).hasClass("show")) .each(function() {
/* eslint-disable no-invalid-this */
if ($(this).hasClass('show')) {
SideContainerToggler.hideInnerContainer($(this)); SideContainerToggler.hideInnerContainer($(this));
}
/* eslint-enable no-invalid-this */
}); });
}
}, },
false); false);
}, },
@ -46,21 +52,26 @@ const SideContainerToggler = {
* toggle * toggle
*/ */
toggle(elementId) { toggle(elementId) {
let elementSelector = $(`#${elementId}`); const elementSelector = $(`#${elementId}`);
let isSelectorVisible = elementSelector.hasClass("show"); const isSelectorVisible = elementSelector.hasClass('show');
if (isSelectorVisible) { if (isSelectorVisible) {
this.hide(); this.hide();
} } else {
else { if (this.isVisible()) {
if (this.isVisible()) $('#sideToolbarContainer').children()
$("#sideToolbarContainer").children().each(function() { .each(function() {
if ($(this).id !== elementId && $(this).hasClass("show")) /* eslint-disable no-invalid-this */
if ($(this).id !== elementId && $(this).hasClass('show')) {
SideContainerToggler.hideInnerContainer($(this)); SideContainerToggler.hideInnerContainer($(this));
}
/* eslint-enable no-invalid-this */
}); });
}
if (!this.isVisible()) if (!this.isVisible()) {
this.show(); this.show();
}
this.showInnerContainer(elementSelector); this.showInnerContainer(elementSelector);
} }
@ -71,7 +82,7 @@ const SideContainerToggler = {
* otherwise returns {false}. * otherwise returns {false}.
*/ */
isVisible() { isVisible() {
return $("#sideToolbarContainer").hasClass("slideInExt"); return $('#sideToolbarContainer').hasClass('slideInExt');
}, },
/** /**
@ -79,24 +90,27 @@ const SideContainerToggler = {
* {false} otherwise. * {false} otherwise.
*/ */
isHovered() { isHovered() {
return $("#sideToolbarContainer:hover").length > 0; return $('#sideToolbarContainer:hover').length > 0;
}, },
/** /**
* Hides the side toolbar panel with a slide out animation. * Hides the side toolbar panel with a slide out animation.
*/ */
hide() { hide() {
$("#sideToolbarContainer") $('#sideToolbarContainer')
.removeClass("slideInExt").addClass("slideOutExt"); .removeClass('slideInExt')
.addClass('slideOutExt');
}, },
/** /**
* Shows the side toolbar panel with a slide in animation. * Shows the side toolbar panel with a slide in animation.
*/ */
show() { show() {
if (!this.isVisible()) if (!this.isVisible()) {
$("#sideToolbarContainer") $('#sideToolbarContainer')
.removeClass("slideOutExt").addClass("slideInExt"); .removeClass('slideOutExt')
.addClass('slideInExt');
}
}, },
/** /**
@ -106,7 +120,7 @@ const SideContainerToggler = {
* element to hide * element to hide
*/ */
hideInnerContainer(containerSelector) { hideInnerContainer(containerSelector) {
containerSelector.removeClass("show").addClass("hide"); containerSelector.removeClass('show').addClass('hide');
this.eventEmitter.emit(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED, this.eventEmitter.emit(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
containerSelector.attr('id'), false); containerSelector.attr('id'), false);
@ -124,12 +138,16 @@ const SideContainerToggler = {
// If we quickly show a container, while another one is animating // If we quickly show a container, while another one is animating
// and animation never ends, so we do not really hide the first one and // and animation never ends, so we do not really hide the first one and
// we end up with to shown panels // we end up with to shown panels
$("#sideToolbarContainer").children().each(function() { $('#sideToolbarContainer').children()
if ($(this).hasClass("show")) .each(function() {
/* eslint-disable no-invalid-this */
if ($(this).hasClass('show')) {
SideContainerToggler.hideInnerContainer($(this)); SideContainerToggler.hideInnerContainer($(this));
}
/* eslint-enable no-invalid-this */
}); });
containerSelector.removeClass("hide").addClass("show"); containerSelector.removeClass('hide').addClass('show');
this.eventEmitter.emit(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED, this.eventEmitter.emit(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
containerSelector.attr('id'), true); containerSelector.attr('id'), true);
@ -138,9 +156,9 @@ const SideContainerToggler = {
/** /**
* TO FIX: do we need to resize the chat? * TO FIX: do we need to resize the chat?
*/ */
resize () { resize() {
//let [width, height] = UIUtil.getSidePanelSize(); // let [width, height] = UIUtil.getSidePanelSize();
//Chat.resizeChat(width, height); // Chat.resizeChat(width, height);
} }
}; };

View File

@ -5,7 +5,7 @@ import ContactListView from './contactlist/ContactListView';
import { isButtonEnabled } from '../../../react/features/toolbox'; import { isButtonEnabled } from '../../../react/features/toolbox';
const SidePanels = { const SidePanels = {
init (eventEmitter) { init(eventEmitter) {
// Initialize chat // Initialize chat
if (isButtonEnabled('chat')) { if (isButtonEnabled('chat')) {
Chat.init(eventEmitter); Chat.init(eventEmitter);

View File

@ -1,8 +1,8 @@
/* global APP, $ */ /* global APP, $ */
import {processReplacements, linkify} from './Replacement'; import { processReplacements, linkify } from './Replacement';
import CommandsProcessor from './Commands'; import CommandsProcessor from './Commands';
import VideoLayout from "../../videolayout/VideoLayout"; import VideoLayout from '../../videolayout/VideoLayout';
import UIUtil from '../../util/UIUtil'; import UIUtil from '../../util/UIUtil';
import UIEvents from '../../../../service/UI/UIEvents'; import UIEvents from '../../../../service/UI/UIEvents';
@ -36,6 +36,9 @@ const htmlStr = `
</div> </div>
</div>`; </div>`;
/**
*
*/
function initHTML() { function initHTML() {
$(`#${sidePanelsContainerId}`) $(`#${sidePanelsContainerId}`)
.append(htmlStr); .append(htmlStr);
@ -44,7 +47,7 @@ function initHTML() {
/** /**
* The container id, which is and the element id. * The container id, which is and the element id.
*/ */
var CHAT_CONTAINER_ID = "chat_container"; const CHAT_CONTAINER_ID = 'chat_container';
/** /**
* Updates visual notification, indicating that a message has arrived. * Updates visual notification, indicating that a message has arrived.
@ -66,19 +69,15 @@ function updateVisualNotification() {
= document.getElementById('toolbar_button_chat'); = document.getElementById('toolbar_button_chat');
const leftIndent const leftIndent
= (UIUtil.getTextWidth(chatButtonElement) = (UIUtil.getTextWidth(chatButtonElement)
- UIUtil.getTextWidth(unreadMsgElement)) - UIUtil.getTextWidth(unreadMsgElement)) / 2;
/ 2;
const topIndent const topIndent
= (UIUtil.getTextHeight(chatButtonElement) = ((UIUtil.getTextHeight(chatButtonElement)
- UIUtil.getTextHeight(unreadMsgElement)) - UIUtil.getTextHeight(unreadMsgElement)) / 2) - 5;
/ 2
- 5;
unreadMsgElement.setAttribute( unreadMsgElement.setAttribute(
'style', 'style',
'top:' + topIndent + '; left:' + leftIndent + ';'); `top:${topIndent}; left:${leftIndent};`);
} } else {
else {
unreadMsgSelector.html(''); unreadMsgSelector.html('');
} }
@ -93,37 +92,49 @@ function updateVisualNotification() {
* @returns {string} * @returns {string}
*/ */
function getCurrentTime(stamp) { function getCurrentTime(stamp) {
var now = (stamp? new Date(stamp): new Date()); const now = stamp ? new Date(stamp) : new Date();
var hour = now.getHours(); let hour = now.getHours();
var minute = now.getMinutes(); let minute = now.getMinutes();
var second = now.getSeconds(); let second = now.getSeconds();
if(hour.toString().length === 1) {
hour = '0'+hour; if (hour.toString().length === 1) {
hour = `0${hour}`;
} }
if(minute.toString().length === 1) { if (minute.toString().length === 1) {
minute = '0'+minute; minute = `0${minute}`;
} }
if(second.toString().length === 1) { if (second.toString().length === 1) {
second = '0'+second; second = `0${second}`;
} }
return hour+':'+minute+':'+second;
return `${hour}:${minute}:${second}`;
} }
/**
*
*/
function toggleSmileys() { function toggleSmileys() {
var smileys = $('#smileysContainer'); const smileys = $('#smileysContainer'); // eslint-disable-line no-shadow
if(!smileys.is(':visible')) {
smileys.show("slide", { direction: "down", duration: 300}); if (smileys.is(':visible')) {
smileys.hide('slide', { direction: 'down',
duration: 300 });
} else { } else {
smileys.hide("slide", { direction: "down", duration: 300}); smileys.show('slide', { direction: 'down',
duration: 300 });
} }
$('#usermsg').focus(); $('#usermsg').focus();
} }
/**
*
*/
function addClickFunction(smiley, number) { function addClickFunction(smiley, number) {
smiley.onclick = function addSmileyToMessage() { smiley.onclick = function addSmileyToMessage() {
var usermsg = $('#usermsg'); const usermsg = $('#usermsg');
var message = usermsg.val(); let message = usermsg.val();
message += smileys['smiley' + number];
message += smileys[`smiley${number}`];
usermsg.val(message); usermsg.val(message);
usermsg.get(0).setSelectionRange(message.length, message.length); usermsg.get(0).setSelectionRange(message.length, message.length);
toggleSmileys(); toggleSmileys();
@ -135,35 +146,38 @@ function addClickFunction(smiley, number) {
* Adds the smileys container to the chat * Adds the smileys container to the chat
*/ */
function addSmileys() { function addSmileys() {
var smileysContainer = document.createElement('div'); const smileysContainer = document.createElement('div');
smileysContainer.id = 'smileysContainer'; smileysContainer.id = 'smileysContainer';
for(var i = 1; i <= 21; i++) { for (let i = 1; i <= 21; i++) {
var smileyContainer = document.createElement('div'); const smileyContainer = document.createElement('div');
smileyContainer.id = 'smiley' + i;
smileyContainer.id = `smiley${i}`;
smileyContainer.className = 'smileyContainer'; smileyContainer.className = 'smileyContainer';
var smiley = document.createElement('img'); const smiley = document.createElement('img');
smiley.src = 'images/smileys/smiley' + i + '.svg';
smiley.className = 'smiley'; smiley.src = `images/smileys/smiley${i}.svg`;
smiley.className = 'smiley';
addClickFunction(smiley, i); addClickFunction(smiley, i);
smileyContainer.appendChild(smiley); smileyContainer.appendChild(smiley);
smileysContainer.appendChild(smileyContainer); smileysContainer.appendChild(smileyContainer);
} }
$("#chat_container").append(smileysContainer); $('#chat_container').append(smileysContainer);
} }
/** /**
* Resizes the chat conversation. * Resizes the chat conversation.
*/ */
function resizeChatConversation() { function resizeChatConversation() {
var msgareaHeight = $('#usermsg').outerHeight(); const msgareaHeight = $('#usermsg').outerHeight();
var chatspace = $('#' + CHAT_CONTAINER_ID); const chatspace = $(`#${CHAT_CONTAINER_ID}`);
var width = chatspace.width(); const width = chatspace.width();
var chat = $('#chatconversation'); const chat = $('#chatconversation');
var smileys = $('#smileysarea'); const smileys = $('#smileysarea'); // eslint-disable-line no-shadow
smileys.height(msgareaHeight); smileys.height(msgareaHeight);
$("#smileys").css('bottom', (msgareaHeight - 26) / 2); $('#smileys').css('bottom', (msgareaHeight - 26) / 2);
$('#smileysContainer').css('bottom', msgareaHeight); $('#smileysContainer').css('bottom', msgareaHeight);
chat.width(width - 10); chat.width(width - 10);
chat.height(window.innerHeight - 15 - msgareaHeight); chat.height(window.innerHeight - 15 - msgareaHeight);
@ -175,63 +189,71 @@ function resizeChatConversation() {
* *
* @param id {string} input id * @param id {string} input id
*/ */
function deferredFocus(id){ function deferredFocus(id) {
setTimeout(() => $(`#${id}`).focus(), 400); setTimeout(() => $(`#${id}`).focus(), 400);
} }
/** /**
* Chat related user interface. * Chat related user interface.
*/ */
var Chat = { const Chat = {
/** /**
* Initializes chat related interface. * Initializes chat related interface.
*/ */
init (eventEmitter) { init(eventEmitter) {
initHTML(); initHTML();
if (APP.conference.getLocalDisplayName()) { if (APP.conference.getLocalDisplayName()) {
Chat.setChatConversationMode(true); Chat.setChatConversationMode(true);
} }
$("#smileys").click(function() { $('#smileys').click(() => {
Chat.toggleSmileys(); Chat.toggleSmileys();
}); });
$('#nickinput').keydown(function (event) { $('#nickinput').keydown(function(event) {
if (event.keyCode === 13) { if (event.keyCode === 13) {
event.preventDefault(); event.preventDefault();
let val = this.value; const val = this.value; // eslint-disable-line no-invalid-this
this.value = '';
this.value = '';// eslint-disable-line no-invalid-this
eventEmitter.emit(UIEvents.NICKNAME_CHANGED, val); eventEmitter.emit(UIEvents.NICKNAME_CHANGED, val);
deferredFocus('usermsg'); deferredFocus('usermsg');
} }
}); });
var usermsg = $('#usermsg'); const usermsg = $('#usermsg');
usermsg.keydown(function (event) {
usermsg.keydown(function(event) {
if (event.keyCode === 13) { if (event.keyCode === 13) {
event.preventDefault(); event.preventDefault();
var value = this.value; const value = this.value; // eslint-disable-line no-invalid-this
usermsg.val('').trigger('autosize.resize'); usermsg.val('').trigger('autosize.resize');
this.focus(); this.focus();// eslint-disable-line no-invalid-this
var command = new CommandsProcessor(value, eventEmitter); const command = new CommandsProcessor(value, eventEmitter);
if (command.isCommand()) { if (command.isCommand()) {
command.processCommand(); command.processCommand();
} else { } else {
var message = UIUtil.escapeHtml(value); const message = UIUtil.escapeHtml(value);
eventEmitter.emit(UIEvents.MESSAGE_CREATED, message); eventEmitter.emit(UIEvents.MESSAGE_CREATED, message);
} }
} }
}); });
var onTextAreaResize = function () { const onTextAreaResize = function() {
resizeChatConversation(); resizeChatConversation();
Chat.scrollChatToBottom(); Chat.scrollChatToBottom();
}; };
usermsg.autosize({callback: onTextAreaResize});
usermsg.autosize({ callback: onTextAreaResize });
eventEmitter.on(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED, eventEmitter.on(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
function(containerId, isVisible) { (containerId, isVisible) => {
if (containerId !== CHAT_CONTAINER_ID || !isVisible) if (containerId !== CHAT_CONTAINER_ID || !isVisible) {
return; return;
}
unreadMessages = 0; unreadMessages = 0;
updateVisualNotification(); updateVisualNotification();
@ -257,13 +279,14 @@ var Chat = {
/** /**
* Appends the given message to the chat conversation. * Appends the given message to the chat conversation.
*/ */
updateChatConversation (id, displayName, message, stamp) { // eslint-disable-next-line max-params
var divClassName = ''; updateChatConversation(id, displayName, message, stamp) {
let divClassName = '';
if (APP.conference.isLocalId(id)) { if (APP.conference.isLocalId(id)) {
divClassName = "localuser"; divClassName = 'localuser';
} else { } else {
divClassName = "remoteuser"; divClassName = 'remoteuser';
if (!Chat.isVisible()) { if (!Chat.isVisible()) {
unreadMessages++; unreadMessages++;
@ -275,22 +298,25 @@ var Chat = {
// replace links and smileys // replace links and smileys
// Strophe already escapes special symbols on sending, // Strophe already escapes special symbols on sending,
// so we escape here only tags to avoid double &amp; // so we escape here only tags to avoid double &amp;
var escMessage = message.replace(/</g, '&lt;'). const escMessage = message.replace(/</g, '&lt;')
replace(/>/g, '&gt;').replace(/\n/g, '<br/>'); .replace(/>/g, '&gt;')
var escDisplayName = UIUtil.escapeHtml(displayName); .replace(/\n/g, '<br/>');
const escDisplayName = UIUtil.escapeHtml(displayName);
// eslint-disable-next-line no-param-reassign
message = processReplacements(escMessage); message = processReplacements(escMessage);
var messageContainer = const messageContainer
'<div class="chatmessage">'+ = `${'<div class="chatmessage">'
'<img src="images/chatArrow.svg" class="chatArrow">' + + '<img src="images/chatArrow.svg" class="chatArrow">'
'<div class="username ' + divClassName +'">' + escDisplayName + + '<div class="username '}${divClassName}">${escDisplayName
'</div>' + '<div class="timestamp">' + getCurrentTime(stamp) + }</div><div class="timestamp">${getCurrentTime(stamp)
'</div>' + '<div class="usermessage">' + message + '</div>' + }</div><div class="usermessage">${message}</div>`
'</div>'; + '</div>';
$('#chatconversation').append(messageContainer); $('#chatconversation').append(messageContainer);
$('#chatconversation').animate( $('#chatconversation').animate(
{ scrollTop: $('#chatconversation')[0].scrollHeight}, 1000); { scrollTop: $('#chatconversation')[0].scrollHeight }, 1000);
}, },
/** /**
@ -298,25 +324,28 @@ var Chat = {
* @param errorMessage the received error message. * @param errorMessage the received error message.
* @param originalText the original message. * @param originalText the original message.
*/ */
chatAddError (errorMessage, originalText) { chatAddError(errorMessage, originalText) {
// eslint-disable-next-line no-param-reassign
errorMessage = UIUtil.escapeHtml(errorMessage); errorMessage = UIUtil.escapeHtml(errorMessage);
// eslint-disable-next-line no-param-reassign
originalText = UIUtil.escapeHtml(originalText); originalText = UIUtil.escapeHtml(originalText);
$('#chatconversation').append( $('#chatconversation').append(
'<div class="errorMessage"><b>Error: </b>' + 'Your message' + `${'<div class="errorMessage"><b>Error: </b>Your message'}${
(originalText? (` "${originalText}"`) : "") + originalText ? ` "${originalText}"` : ''
' was not sent.' + } was not sent.${
(errorMessage? (' Reason: ' + errorMessage) : '') + '</div>'); errorMessage ? ` Reason: ${errorMessage}` : ''}</div>`);
$('#chatconversation').animate( $('#chatconversation').animate(
{ scrollTop: $('#chatconversation')[0].scrollHeight}, 1000); { scrollTop: $('#chatconversation')[0].scrollHeight }, 1000);
}, },
/** /**
* Sets the subject to the UI * Sets the subject to the UI
* @param subject the subject * @param subject the subject
*/ */
setSubject (subject) { setSubject(subject) {
if (subject) { if (subject) {
// eslint-disable-next-line no-param-reassign
subject = subject.trim(); subject = subject.trim();
} }
@ -332,16 +361,17 @@ var Chat = {
* @param {boolean} isConversationMode if chat should be in * @param {boolean} isConversationMode if chat should be in
* conversation mode or not. * conversation mode or not.
*/ */
setChatConversationMode (isConversationMode) { setChatConversationMode(isConversationMode) {
$('#' + CHAT_CONTAINER_ID) $(`#${CHAT_CONTAINER_ID}`)
.toggleClass('is-conversation-mode', isConversationMode); .toggleClass('is-conversation-mode', isConversationMode);
}, },
/** /**
* Resizes the chat area. * Resizes the chat area.
*/ */
resizeChat (width, height) { resizeChat(width, height) {
$('#' + CHAT_CONTAINER_ID).width(width).height(height); $(`#${CHAT_CONTAINER_ID}`).width(width)
.height(height);
resizeChatConversation(); resizeChatConversation();
}, },
@ -349,10 +379,11 @@ var Chat = {
/** /**
* Indicates if the chat is currently visible. * Indicates if the chat is currently visible.
*/ */
isVisible () { isVisible() {
return UIUtil.isVisible( return UIUtil.isVisible(
document.getElementById(CHAT_CONTAINER_ID)); document.getElementById(CHAT_CONTAINER_ID));
}, },
/** /**
* Shows and hides the window with the smileys * Shows and hides the window with the smileys
*/ */
@ -361,7 +392,7 @@ var Chat = {
/** /**
* Scrolls chat to the bottom. * Scrolls chat to the bottom.
*/ */
scrollChatToBottom () { scrollChatToBottom() {
setTimeout( setTimeout(
() => { () => {
const chatconversation = $('#chatconversation'); const chatconversation = $('#chatconversation');

View File

@ -7,7 +7,7 @@ import UIEvents from '../../../../service/UI/UIEvents';
* @type {{String: function}} * @type {{String: function}}
*/ */
const commands = { const commands = {
"topic" : processTopic 'topic': processTopic
}; };
/** /**
@ -16,13 +16,15 @@ const commands = {
* @returns {string} the command * @returns {string} the command
*/ */
function getCommand(message) { function getCommand(message) {
if(message) { if (message) {
for(var command in commands) { for (const command in commands) {
if(message.indexOf("/" + command) === 0) if (message.indexOf(`/${command}`) === 0) {
return command; return command;
}
} }
} }
return "";
return '';
} }
/** /**
@ -30,7 +32,8 @@ function getCommand(message) {
* @param commandArguments the arguments of the topic command. * @param commandArguments the arguments of the topic command.
*/ */
function processTopic(commandArguments, emitter) { function processTopic(commandArguments, emitter) {
var topic = UIUtil.escapeHtml(commandArguments); const topic = UIUtil.escapeHtml(commandArguments);
emitter.emit(UIEvents.SUBJECT_CHANGED, topic); emitter.emit(UIEvents.SUBJECT_CHANGED, topic);
} }
@ -41,7 +44,7 @@ function processTopic(commandArguments, emitter) {
* @constructor * @constructor
*/ */
function CommandsProcessor(message, emitter) { function CommandsProcessor(message, emitter) {
var command = getCommand(message); const command = getCommand(message);
this.emitter = emitter; this.emitter = emitter;
@ -54,7 +57,7 @@ function CommandsProcessor(message, emitter) {
}; };
var messageArgument = message.substr(command.length + 2); const messageArgument = message.substr(command.length + 2);
/** /**
* Returns the arguments of the command. * Returns the arguments of the command.
@ -70,8 +73,10 @@ function CommandsProcessor(message, emitter) {
* @returns {boolean} * @returns {boolean}
*/ */
CommandsProcessor.prototype.isCommand = function() { CommandsProcessor.prototype.isCommand = function() {
if (this.getCommand()) if (this.getCommand()) {
return true; return true;
}
return false; return false;
}; };
@ -79,8 +84,9 @@ CommandsProcessor.prototype.isCommand = function() {
* Processes the command. * Processes the command.
*/ */
CommandsProcessor.prototype.processCommand = function() { CommandsProcessor.prototype.processCommand = function() {
if(!this.isCommand()) if (!this.isCommand()) {
return; return;
}
commands[this.getCommand()](this.getArgument(), this.emitter); commands[this.getCommand()](this.getArgument(), this.emitter);
}; };

View File

@ -1,17 +1,11 @@
/* jshint -W101 */
import { regexes } from './smileys'; import { regexes } from './smileys';
/** /**
* Processes links and smileys in "body" * Processes links and smileys in "body"
*/ */
export function processReplacements(body) { export function processReplacements(body) {
//make links clickable // make links clickable + add smileys
body = linkify(body); return smilify(linkify(body));
//add smileys
body = smilify(body);
return body;
} }
/** /**
@ -19,20 +13,23 @@ export function processReplacements(body) {
* with their <a href=""></a> * with their <a href=""></a>
*/ */
export function linkify(inputText) { export function linkify(inputText) {
var replacedText, replacePattern1, replacePattern2, replacePattern3; let replacedText;
/* eslint-disable no-useless-escape */ /* eslint-disable no-useless-escape, max-len */
// URLs starting with http://, https://, or ftp://
const replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
//URLs starting with http://, https://, or ftp://
replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
replacedText = inputText.replace(replacePattern1, '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>'); replacedText = inputText.replace(replacePattern1, '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>');
//URLs starting with "www." (without // before it, or it'd re-link the ones done above). // URLs starting with "www." (without // before it, or it'd re-link the ones done above).
replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim; const replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
replacedText = replacedText.replace(replacePattern2, '$1<a href="https://$2" target="_blank" rel="noopener noreferrer">$2</a>'); replacedText = replacedText.replace(replacePattern2, '$1<a href="https://$2" target="_blank" rel="noopener noreferrer">$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; const replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>'); replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>');
/* eslint-enable no-useless-escape */ /* eslint-enable no-useless-escape */
@ -44,14 +41,16 @@ export function linkify(inputText) {
* Replaces common smiley strings with images * Replaces common smiley strings with images
*/ */
function smilify(body) { function smilify(body) {
if(!body) { if (!body) {
return body; return body;
} }
for(var smiley in regexes) { let formattedBody = body;
if(regexes.hasOwnProperty(smiley)) {
body = body.replace(regexes[smiley], for (const smiley in regexes) {
'<img class="smiley" src="images/smileys/' + smiley + '.svg">'); if (regexes.hasOwnProperty(smiley)) {
formattedBody = formattedBody.replace(regexes[smiley],
`<img class="smiley" src="images/smileys/${smiley}.svg">`);
} }
} }

View File

@ -1,25 +1,25 @@
export const smileys = { export const smileys = {
smiley1: ":)", smiley1: ':)',
smiley2: ":(", smiley2: ':(',
smiley3: ":D", smiley3: ':D',
smiley4: "(y)", smiley4: '(y)',
smiley5: " :P", smiley5: ' :P',
smiley6: "(wave)", smiley6: '(wave)',
smiley7: "(blush)", smiley7: '(blush)',
smiley8: "(chuckle)", smiley8: '(chuckle)',
smiley9: "(shocked)", smiley9: '(shocked)',
smiley10: ":*", smiley10: ':*',
smiley11: "(n)", smiley11: '(n)',
smiley12: "(search)", smiley12: '(search)',
smiley13: " <3", smiley13: ' <3',
smiley14: "(oops)", smiley14: '(oops)',
smiley15: "(angry)", smiley15: '(angry)',
smiley16: "(angel)", smiley16: '(angel)',
smiley17: "(sick)", smiley17: '(sick)',
smiley18: ";(", smiley18: ';(',
smiley19: "(bomb)", smiley19: '(bomb)',
smiley20: "(clap)", smiley20: '(clap)',
smiley21: " ;)" smiley21: ' ;)'
}; };
export const regexes = { export const regexes = {

View File

@ -19,7 +19,7 @@ import UIUtil from '../../util/UIUtil';
* the term "contact" is not used elsewhere. Normally people in the conference * the term "contact" is not used elsewhere. Normally people in the conference
* are internally refered to as "participants" or externally as "members". * are internally refered to as "participants" or externally as "members".
*/ */
var ContactListView = { const ContactListView = {
/** /**
* Creates and appends the contact list to the side panel. * Creates and appends the contact list to the side panel.
* *
@ -33,7 +33,6 @@ var ContactListView = {
$('#sideToolbarContainer').append(contactListPanelContainer); $('#sideToolbarContainer').append(contactListPanelContainer);
/* jshint ignore:start */
ReactDOM.render( ReactDOM.render(
<Provider store = { APP.store }> <Provider store = { APP.store }>
<I18nextProvider i18n = { i18next }> <I18nextProvider i18n = { i18next }>
@ -42,7 +41,6 @@ var ContactListView = {
</Provider>, </Provider>,
contactListPanelContainer contactListPanelContainer
); );
/* jshint ignore:end */
}, },
/** /**
@ -50,8 +48,8 @@ var ContactListView = {
* *
* @return {boolean) true if the contact list is currently visible. * @return {boolean) true if the contact list is currently visible.
*/ */
isVisible () { isVisible() {
return UIUtil.isVisible(document.getElementById("contactlist")); return UIUtil.isVisible(document.getElementById('contactlist'));
} }
}; };

View File

@ -1,98 +1,117 @@
/* global $, APP */ /* global $, APP */
import UIUtil from "../../util/UIUtil"; import UIUtil from '../../util/UIUtil';
import UIEvents from "../../../../service/UI/UIEvents"; import UIEvents from '../../../../service/UI/UIEvents';
import Settings from '../../../settings/Settings'; import Settings from '../../../settings/Settings';
import { sendEvent } from '../../../../react/features/analytics'; import { sendEvent } from '../../../../react/features/analytics';
const sidePanelsContainerId = 'sideToolbarContainer'; const sidePanelsContainerId = 'sideToolbarContainer';
const htmlStr = ` const htmlStr = `
<div id="profile_container" class="sideToolbarContainer__inner"> <div id='profile_container' class='sideToolbarContainer__inner'>
<div class="title" data-i18n="profile.title"></div> <div class='title' data-i18n='profile.title'></div>
<div class="sideToolbarBlock first"> <div class='sideToolbarBlock first'>
<label class="first" data-i18n="profile.setDisplayNameLabel"> <label class='first' data-i18n='profile.setDisplayNameLabel'>
</label> </label>
<input class="input-control" type="text" id="setDisplayName" <input class='input-control' type='text' id='setDisplayName'
data-i18n="[placeholder]settings.name"> data-i18n='[placeholder]settings.name'>
</div> </div>
<div class="sideToolbarBlock"> <div class='sideToolbarBlock'>
<label data-i18n="profile.setEmailLabel"></label> <label data-i18n='profile.setEmailLabel'></label>
<input id="setEmail" type="text" class="input-control" <input id='setEmail' type='text' class='input-control'
data-i18n="[placeholder]profile.setEmailInput"> data-i18n='[placeholder]profile.setEmailInput'>
</div> </div>
<div id="profile_auth_container" <div id='profile_auth_container'
class="sideToolbarBlock auth_container"> class='sideToolbarBlock auth_container'>
<p data-i18n="toolbar.authenticate"></p> <p data-i18n='toolbar.authenticate'></p>
<ul> <ul>
<li id="profile_auth_identity"></li> <li id='profile_auth_identity'></li>
<li id="profile_button_login"> <li id='profile_button_login'>
<a class="authButton" data-i18n="toolbar.login"></a> <a class='authButton' data-i18n='toolbar.login'></a>
</li> </li>
<li id="profile_button_logout"> <li id='profile_button_logout'>
<a class="authButton" data-i18n="toolbar.logout"></a> <a class='authButton' data-i18n='toolbar.logout'></a>
</li> </li>
</ul> </ul>
</div> </div>
</div>`; </div>`;
/**
*
*/
function initHTML() { function initHTML() {
$(`#${sidePanelsContainerId}`) $(`#${sidePanelsContainerId}`)
.append(htmlStr); .append(htmlStr);
// make sure we translate the panel, as adding it can be after i18n // make sure we translate the panel, as adding it can be after i18n
// library had initialized and translated already present html // library had initialized and translated already present html
APP.translation.translateElement($(`#${sidePanelsContainerId}`)); APP.translation.translateElement($(`#${sidePanelsContainerId}`));
} }
export default { export default {
init (emitter) { init(emitter) {
initHTML(); initHTML();
// DISPLAY NAME
function updateDisplayName () { /**
* Updates display name.
*
* @returns {void}
*/
function updateDisplayName() {
emitter.emit(UIEvents.NICKNAME_CHANGED, $('#setDisplayName').val()); emitter.emit(UIEvents.NICKNAME_CHANGED, $('#setDisplayName').val());
} }
$('#setDisplayName') $('#setDisplayName')
.val(Settings.getDisplayName()) .val(Settings.getDisplayName())
.keyup(function (event) { .keyup(event => {
if (event.keyCode === 13) { // enter if (event.keyCode === 13) { // enter
updateDisplayName(); updateDisplayName();
} }
}) })
.focusout(updateDisplayName); .focusout(updateDisplayName);
/**
// EMAIL * Updates the email.
function updateEmail () { *
* @returns {void}
*/
function updateEmail() {
emitter.emit(UIEvents.EMAIL_CHANGED, $('#setEmail').val()); emitter.emit(UIEvents.EMAIL_CHANGED, $('#setEmail').val());
} }
$('#setEmail') $('#setEmail')
.val(Settings.getEmail()) .val(Settings.getEmail())
.keyup(function (event) { .keyup(event => {
if (event.keyCode === 13) { // enter if (event.keyCode === 13) { // enter
updateEmail(); updateEmail();
} }
}).focusout(updateEmail); })
.focusout(updateEmail);
// LOGIN /**
function loginClicked () { *
*/
function loginClicked() {
sendEvent('authenticate.login.clicked'); sendEvent('authenticate.login.clicked');
emitter.emit(UIEvents.AUTH_CLICKED); emitter.emit(UIEvents.AUTH_CLICKED);
} }
$('#profile_button_login').click(loginClicked); $('#profile_button_login').click(loginClicked);
// LOGOUT /**
function logoutClicked () { *
let titleKey = "dialog.logoutTitle"; */
let msgKey = "dialog.logoutQuestion"; function logoutClicked() {
const titleKey = 'dialog.logoutTitle';
const msgKey = 'dialog.logoutQuestion';
sendEvent('authenticate.logout.clicked'); sendEvent('authenticate.logout.clicked');
// Ask for confirmation // Ask for confirmation
APP.UI.messageHandler.openTwoButtonDialog({ APP.UI.messageHandler.openTwoButtonDialog({
titleKey: titleKey, titleKey,
msgKey: msgKey, msgKey,
leftButtonKey: "dialog.Yes", leftButtonKey: 'dialog.Yes',
submitFunction: function (evt, yes) { submitFunction(evt, yes) {
if (yes) { if (yes) {
emitter.emit(UIEvents.LOGOUT); emitter.emit(UIEvents.LOGOUT);
} }
@ -107,15 +126,15 @@ export default {
* Check if settings menu is visible or not. * Check if settings menu is visible or not.
* @returns {boolean} * @returns {boolean}
*/ */
isVisible () { isVisible() {
return UIUtil.isVisible(document.getElementById("profile_container")); return UIUtil.isVisible(document.getElementById('profile_container'));
}, },
/** /**
* Change user display name in the settings menu. * Change user display name in the settings menu.
* @param {string} newDisplayName * @param {string} newDisplayName
*/ */
changeDisplayName (newDisplayName) { changeDisplayName(newDisplayName) {
$('#setDisplayName').val(newDisplayName); $('#setDisplayName').val(newDisplayName);
}, },
@ -123,7 +142,7 @@ export default {
* Change user avatar in the settings menu. * Change user avatar in the settings menu.
* @param {string} avatarUrl url of the new avatar * @param {string} avatarUrl url of the new avatar
*/ */
changeAvatar (avatarUrl) { changeAvatar(avatarUrl) {
$('#avatar').attr('src', avatarUrl); $('#avatar').attr('src', avatarUrl);
}, },
@ -131,7 +150,7 @@ export default {
* Change the value of the field for the user email. * Change the value of the field for the user email.
* @param {string} email the new value that will be displayed in the field. * @param {string} email the new value that will be displayed in the field.
*/ */
changeEmail (email) { changeEmail(email) {
$('#setEmail').val(email); $('#setEmail').val(email);
}, },
@ -139,8 +158,9 @@ export default {
* Shows or hides authentication related buttons * Shows or hides authentication related buttons
* @param {boolean} show <tt>true</tt> to show or <tt>false</tt> to hide * @param {boolean} show <tt>true</tt> to show or <tt>false</tt> to hide
*/ */
showAuthenticationButtons (show) { showAuthenticationButtons(show) {
let id = 'profile_auth_container'; const id = 'profile_auth_container';
UIUtil.setVisible(id, show); UIUtil.setVisible(id, show);
}, },
@ -148,8 +168,8 @@ export default {
* Shows/hides login button. * Shows/hides login button.
* @param {boolean} show <tt>true</tt> to show or <tt>false</tt> to hide * @param {boolean} show <tt>true</tt> to show or <tt>false</tt> to hide
*/ */
showLoginButton (show) { showLoginButton(show) {
let id = 'profile_button_login'; const id = 'profile_button_login';
UIUtil.setVisible(id, show); UIUtil.setVisible(id, show);
}, },
@ -158,8 +178,8 @@ export default {
* Shows/hides logout button. * Shows/hides logout button.
* @param {boolean} show <tt>true</tt> to show or <tt>false</tt> to hide * @param {boolean} show <tt>true</tt> to show or <tt>false</tt> to hide
*/ */
showLogoutButton (show) { showLogoutButton(show) {
let id = 'profile_button_logout'; const id = 'profile_button_logout';
UIUtil.setVisible(id, show); UIUtil.setVisible(id, show);
}, },
@ -168,10 +188,10 @@ export default {
* Displays user's authenticated identity name (login). * Displays user's authenticated identity name (login).
* @param {string} authIdentity identity name to be displayed. * @param {string} authIdentity identity name to be displayed.
*/ */
setAuthenticatedIdentity (authIdentity) { setAuthenticatedIdentity(authIdentity) {
let id = 'profile_auth_identity'; const id = 'profile_auth_identity';
UIUtil.setVisible(id, !!authIdentity); UIUtil.setVisible(id, Boolean(authIdentity));
$(`#${id}`).text(authIdentity ? authIdentity : ''); $(`#${id}`).text(authIdentity ? authIdentity : '');
} }

View File

@ -1,10 +1,10 @@
/* global $, APP, AJS, interfaceConfig */ /* global $, APP, AJS, interfaceConfig */
import { LANGUAGES } from "../../../../react/features/base/i18n"; import { LANGUAGES } from '../../../../react/features/base/i18n';
import { openDeviceSelectionDialog } import { openDeviceSelectionDialog }
from '../../../../react/features/device-selection'; from '../../../../react/features/device-selection';
import UIUtil from "../../util/UIUtil"; import UIUtil from '../../util/UIUtil';
import UIEvents from "../../../../service/UI/UIEvents"; import UIEvents from '../../../../service/UI/UIEvents';
const sidePanelsContainerId = 'sideToolbarContainer'; const sidePanelsContainerId = 'sideToolbarContainer';
const deviceSelectionButtonClasses const deviceSelectionButtonClasses
@ -13,12 +13,12 @@ const htmlStr = `
<div id="settings_container" class="sideToolbarContainer__inner"> <div id="settings_container" class="sideToolbarContainer__inner">
<div class="title" data-i18n="settings.title"></div> <div class="title" data-i18n="settings.title"></div>
<form class="aui"> <form class="aui">
<div id="languagesSelectWrapper" <div id="languagesSelectWrapper"
class="sideToolbarBlock first hide"> class="sideToolbarBlock first hide">
<select id="languagesSelect"></select> <select id="languagesSelect"></select>
</div> </div>
<div id="deviceOptionsWrapper" class="hide"> <div id="deviceOptionsWrapper" class="hide">
<div id="deviceOptionsTitle" class="subTitle hide" <div id="deviceOptionsTitle" class="subTitle hide"
data-i18n="settings.audioVideo"></div> data-i18n="settings.audioVideo"></div>
<div class="sideToolbarBlock first"> <div class="sideToolbarBlock first">
<button <button
@ -29,24 +29,24 @@ const htmlStr = `
</div> </div>
</div> </div>
<div id="moderatorOptionsWrapper" class="hide"> <div id="moderatorOptionsWrapper" class="hide">
<div id="moderatorOptionsTitle" class="subTitle hide" <div id="moderatorOptionsTitle" class="subTitle hide"
data-i18n="settings.moderator"></div> data-i18n="settings.moderator"></div>
<div id="startMutedOptions" class="hide"> <div id="startMutedOptions" class="hide">
<div class="sideToolbarBlock first"> <div class="sideToolbarBlock first">
<input type="checkbox" id="startAudioMuted"> <input type="checkbox" id="startAudioMuted">
<label class="startMutedLabel" for="startAudioMuted" <label class="startMutedLabel" for="startAudioMuted"
data-i18n="settings.startAudioMuted"></label> data-i18n="settings.startAudioMuted"></label>
</div> </div>
<div class="sideToolbarBlock"> <div class="sideToolbarBlock">
<input type="checkbox" id="startVideoMuted"> <input type="checkbox" id="startVideoMuted">
<label class="startMutedLabel" for="startVideoMuted" <label class="startMutedLabel" for="startVideoMuted"
data-i18n="settings.startVideoMuted"></label> data-i18n="settings.startVideoMuted"></label>
</div> </div>
</div> </div>
<div id="followMeOptions" class="hide"> <div id="followMeOptions" class="hide">
<div class="sideToolbarBlock"> <div class="sideToolbarBlock">
<input type="checkbox" id="followMeCheckBox"> <input type="checkbox" id="followMeCheckBox">
<label class="followMeLabel" for="followMeCheckBox" <label class="followMeLabel" for="followMeCheckBox"
data-i18n="settings.followMe"></label> data-i18n="settings.followMe"></label>
</div> </div>
</div> </div>
@ -54,9 +54,13 @@ const htmlStr = `
</form> </form>
</div>`; </div>`;
/**
*
*/
function initHTML() { function initHTML() {
$(`#${sidePanelsContainerId}`) $(`#${sidePanelsContainerId}`)
.append(htmlStr); .append(htmlStr);
// make sure we translate the panel, as adding it can be after i18n // make sure we translate the panel, as adding it can be after i18n
// library had initialized and translated already present html // library had initialized and translated already present html
APP.translation.translateElement($(`#${sidePanelsContainerId}`)); APP.translation.translateElement($(`#${sidePanelsContainerId}`));
@ -70,8 +74,8 @@ function initHTML() {
* @returns {string} * @returns {string}
*/ */
function generateLanguagesOptions(items, currentLang) { function generateLanguagesOptions(items, currentLang) {
return items.map(function (lang) { return items.map(lang => {
let attrs = { const attrs = {
value: lang, value: lang,
'data-i18n': `languages:${lang}` 'data-i18n': `languages:${lang}`
}; };
@ -80,7 +84,9 @@ function generateLanguagesOptions(items, currentLang) {
attrs.selected = 'selected'; attrs.selected = 'selected';
} }
let attrsStr = UIUtil.attrsToString(attrs); const attrsStr = UIUtil.attrsToString(attrs);
return `<option ${attrsStr}></option>`; return `<option ${attrsStr}></option>`;
}).join(''); }).join('');
} }
@ -101,14 +107,15 @@ function initSelect2($el, onSelectedCb) {
} }
export default { export default {
init (emitter) { init(emitter) {
initHTML(); initHTML();
//LANGUAGES BOX
// LANGUAGES BOX
if (UIUtil.isSettingEnabled('language')) { if (UIUtil.isSettingEnabled('language')) {
const wrapperId = 'languagesSelectWrapper'; const wrapperId = 'languagesSelectWrapper';
const selectId = 'languagesSelect'; const selectId = 'languagesSelect';
const selectEl = AJS.$(`#${selectId}`); const selectEl = AJS.$(`#${selectId}`);
let selectInput; let selectInput; // eslint-disable-line prefer-const
selectEl.html(generateLanguagesOptions( selectEl.html(generateLanguagesOptions(
LANGUAGES, LANGUAGES,
@ -121,11 +128,13 @@ export default {
APP.translation.translateElement(selectInput); APP.translation.translateElement(selectInput);
emitter.emit(UIEvents.LANG_CHANGED, val); emitter.emit(UIEvents.LANG_CHANGED, val);
}); });
//find new selectInput element
// find new selectInput element
selectInput = $(`#s2id_${selectId} .select2-chosen`); selectInput = $(`#s2id_${selectId} .select2-chosen`);
//first select fix for languages options
selectInput[0].dataset.i18n = // first select fix for languages options
`languages:${APP.translation.getCurrentLanguage()}`; selectInput[0].dataset.i18n
= `languages:${APP.translation.getCurrentLanguage()}`;
// translate selectInput, which is the currently selected language // translate selectInput, which is the currently selected language
// otherwise there will be no selected option // otherwise there will be no selected option
@ -133,10 +142,13 @@ export default {
APP.translation.translateElement(selectEl); APP.translation.translateElement(selectEl);
APP.translation.addLanguageChangedListener( APP.translation.addLanguageChangedListener(
lng => selectInput[0].dataset.i18n = `languages:${lng}`); lng => {
selectInput[0].dataset.i18n = `languages:${lng}`;
});
UIUtil.setVisible(wrapperId, true); UIUtil.setVisible(wrapperId, true);
} }
// DEVICES LIST // DEVICES LIST
if (UIUtil.isSettingEnabled('devices')) { if (UIUtil.isSettingEnabled('devices')) {
const wrapperId = 'deviceOptionsWrapper'; const wrapperId = 'deviceOptionsWrapper';
@ -145,19 +157,21 @@ export default {
APP.store.dispatch(openDeviceSelectionDialog())); APP.store.dispatch(openDeviceSelectionDialog()));
// Only show the subtitle if this isn't the only setting section. // Only show the subtitle if this isn't the only setting section.
if (interfaceConfig.SETTINGS_SECTIONS.length > 1) if (interfaceConfig.SETTINGS_SECTIONS.length > 1) {
UIUtil.setVisible("deviceOptionsTitle", true); UIUtil.setVisible('deviceOptionsTitle', true);
}
UIUtil.setVisible(wrapperId, true); UIUtil.setVisible(wrapperId, true);
} }
// MODERATOR // MODERATOR
if (UIUtil.isSettingEnabled('moderator')) { if (UIUtil.isSettingEnabled('moderator')) {
const wrapperId = 'moderatorOptionsWrapper'; const wrapperId = 'moderatorOptionsWrapper';
// START MUTED // START MUTED
$("#startMutedOptions").change(function () { $('#startMutedOptions').change(() => {
let startAudioMuted = $("#startAudioMuted").is(":checked"); const startAudioMuted = $('#startAudioMuted').is(':checked');
let startVideoMuted = $("#startVideoMuted").is(":checked"); const startVideoMuted = $('#startVideoMuted').is(':checked');
emitter.emit( emitter.emit(
UIEvents.START_MUTED_CHANGED, UIEvents.START_MUTED_CHANGED,
@ -168,8 +182,10 @@ export default {
// FOLLOW ME // FOLLOW ME
const followMeToggle = document.getElementById('followMeCheckBox'); const followMeToggle = document.getElementById('followMeCheckBox');
followMeToggle.addEventListener('change', () => { followMeToggle.addEventListener('change', () => {
const isFollowMeEnabled = followMeToggle.checked; const isFollowMeEnabled = followMeToggle.checked;
emitter.emit(UIEvents.FOLLOW_ME_ENABLED, isFollowMeEnabled); emitter.emit(UIEvents.FOLLOW_ME_ENABLED, isFollowMeEnabled);
}); });
@ -181,26 +197,28 @@ export default {
* If start audio muted/start video muted options should be visible or not. * If start audio muted/start video muted options should be visible or not.
* @param {boolean} show * @param {boolean} show
*/ */
showStartMutedOptions (show) { showStartMutedOptions(show) {
if (show && UIUtil.isSettingEnabled('moderator')) { if (show && UIUtil.isSettingEnabled('moderator')) {
// Only show the subtitle if this isn't the only setting section. // Only show the subtitle if this isn't the only setting section.
if (!$("#moderatorOptionsTitle").is(":visible") if (!$('#moderatorOptionsTitle').is(':visible')
&& interfaceConfig.SETTINGS_SECTIONS.length > 1) && interfaceConfig.SETTINGS_SECTIONS.length > 1) {
UIUtil.setVisible("moderatorOptionsTitle", true); UIUtil.setVisible('moderatorOptionsTitle', true);
}
UIUtil.setVisible("startMutedOptions", true); UIUtil.setVisible('startMutedOptions', true);
} else { } else {
// Only show the subtitle if this isn't the only setting section. // Only show the subtitle if this isn't the only setting section.
if ($("#moderatorOptionsTitle").is(":visible")) if ($('#moderatorOptionsTitle').is(':visible')) {
UIUtil.setVisible("moderatorOptionsTitle", false); UIUtil.setVisible('moderatorOptionsTitle', false);
}
UIUtil.setVisible("startMutedOptions", false); UIUtil.setVisible('startMutedOptions', false);
} }
}, },
updateStartMutedBox (startAudioMuted, startVideoMuted) { updateStartMutedBox(startAudioMuted, startVideoMuted) {
$("#startAudioMuted").attr("checked", startAudioMuted); $('#startAudioMuted').attr('checked', startAudioMuted);
$("#startVideoMuted").attr("checked", startVideoMuted); $('#startVideoMuted').attr('checked', startVideoMuted);
}, },
/** /**
@ -208,9 +226,9 @@ export default {
* *
* @param {boolean} show {true} to show those options, {false} to hide them * @param {boolean} show {true} to show those options, {false} to hide them
*/ */
showFollowMeOptions (show) { showFollowMeOptions(show) {
UIUtil.setVisible( UIUtil.setVisible(
"followMeOptions", 'followMeOptions',
show && UIUtil.isSettingEnabled('moderator')); show && UIUtil.isSettingEnabled('moderator'));
}, },
@ -218,7 +236,7 @@ export default {
* Check if settings menu is visible or not. * Check if settings menu is visible or not.
* @returns {boolean} * @returns {boolean}
*/ */
isVisible () { isVisible() {
return UIUtil.isVisible(document.getElementById("settings_container")); return UIUtil.isVisible(document.getElementById('settings_container'));
} }
}; };

View File

@ -1,5 +1,5 @@
/* global $, APP */ /* global $, APP */
const logger = require("jitsi-meet-logger").getLogger(__filename); const logger = require('jitsi-meet-logger').getLogger(__filename);
import jitsiLocalStorage from '../../util/JitsiLocalStorage'; import jitsiLocalStorage from '../../util/JitsiLocalStorage';
@ -31,12 +31,14 @@ let twoButtonDialog = null;
* @returns {string} * @returns {string}
*/ */
function generateDontShowCheckbox(options) { function generateDontShowCheckbox(options) {
if(!isDontShowAgainEnabled(options)) { if (!isDontShowAgainEnabled(options)) {
return ""; return '';
} }
let checked const checked
= (options.checked === true) ? "checked" : ""; = options.checked === true ? 'checked' : '';
return `<br /> return `<br />
<label> <label>
<input type='checkbox' ${checked} id='${options.id}' /> <input type='checkbox' ${checked} id='${options.id}' />
@ -54,12 +56,13 @@ function generateDontShowCheckbox(options) {
* false otherwise. * false otherwise.
*/ */
function dontShowTheDialog(options) { function dontShowTheDialog(options) {
if(isDontShowAgainEnabled(options)) { if (isDontShowAgainEnabled(options)) {
if(jitsiLocalStorage.getItem(options.localStorageKey || options.id) if (jitsiLocalStorage.getItem(options.localStorageKey || options.id)
=== "true") { === 'true') {
return true; return true;
} }
} }
return false; return false;
} }
@ -76,24 +79,27 @@ function dontShowTheDialog(options) {
* @returns {Function} wrapped function * @returns {Function} wrapped function
*/ */
function dontShowAgainSubmitFunctionWrapper(options, submitFunction) { function dontShowAgainSubmitFunctionWrapper(options, submitFunction) {
if(isDontShowAgainEnabled(options)) { if (isDontShowAgainEnabled(options)) {
return (...args) => { return (...args) => {
logger.debug(args, options.buttonValues); logger.debug(args, options.buttonValues);
//args[1] is the value associated with the pressed button
if(!options.buttonValues || options.buttonValues.length === 0 // args[1] is the value associated with the pressed button
|| options.buttonValues.indexOf(args[1]) !== -1 ) { if (!options.buttonValues || options.buttonValues.length === 0
let checkbox = $(`#${options.id}`); || options.buttonValues.indexOf(args[1]) !== -1) {
const checkbox = $(`#${options.id}`);
if (checkbox.length) { if (checkbox.length) {
jitsiLocalStorage.setItem( jitsiLocalStorage.setItem(
options.localStorageKey || options.id, options.localStorageKey || options.id,
checkbox.prop("checked")); checkbox.prop('checked'));
} }
} }
submitFunction(...args); submitFunction(...args);
}; };
} else {
return submitFunction;
} }
return submitFunction;
} }
/** /**
@ -102,12 +108,12 @@ function dontShowAgainSubmitFunctionWrapper(options, submitFunction) {
* @returns {boolean} true if enabled and false if not. * @returns {boolean} true if enabled and false if not.
*/ */
function isDontShowAgainEnabled(options) { function isDontShowAgainEnabled(options) {
return typeof options === "object"; return typeof options === 'object';
} }
var messageHandler = { const messageHandler = {
OK: "dialog.OK", OK: 'dialog.OK',
CANCEL: "dialog.Cancel", CANCEL: 'dialog.Cancel',
/** /**
* Shows a message to the user. * Shows a message to the user.
@ -120,25 +126,32 @@ var messageHandler = {
* the prompt is closed (optional) * the prompt is closed (optional)
* @return the prompt that was created, or null * @return the prompt that was created, or null
*/ */
// eslint-disable-next-line max-params
openMessageDialog(titleKey, messageKey, i18nOptions, closeFunction) { openMessageDialog(titleKey, messageKey, i18nOptions, closeFunction) {
if (!popupEnabled) if (!popupEnabled) {
return null; return null;
}
let dialog = $.prompt( const dialog = $.prompt(
APP.translation.generateTranslationHTML(messageKey, i18nOptions), APP.translation.generateTranslationHTML(messageKey, i18nOptions),
{ {
title: this._getFormattedTitleString(titleKey), title: this._getFormattedTitleString(titleKey),
persistent: false, persistent: false,
promptspeed: 0, promptspeed: 0,
classes: this._getDialogClasses(), classes: this._getDialogClasses(),
// eslint-disable-next-line max-params
close(e, v, m, f) { close(e, v, m, f) {
if(closeFunction) if (closeFunction) {
closeFunction(e, v, m, f); closeFunction(e, v, m, f);
}
} }
}); });
APP.translation.translateElement(dialog, i18nOptions); APP.translation.translateElement(dialog, i18nOptions);
return $.prompt.getApi(); return $.prompt.getApi();
}, },
/** /**
* Shows a message to the user with two buttons: first is given as a * Shows a message to the user with two buttons: first is given as a
* parameter and the second is Cancel. * parameter and the second is Cancel.
@ -169,8 +182,8 @@ var messageHandler = {
* storage. if not provided dontShowAgain.id will be used. * storage. if not provided dontShowAgain.id will be used.
* @return the prompt that was created, or null * @return the prompt that was created, or null
*/ */
openTwoButtonDialog: function(options) { openTwoButtonDialog(options) {
let { const {
titleKey, titleKey,
msgKey, msgKey,
msgString, msgString,
@ -182,32 +195,40 @@ var messageHandler = {
size, size,
defaultButton, defaultButton,
wrapperClass, wrapperClass,
classes,
dontShowAgain dontShowAgain
} = options; } = options;
if (!popupEnabled || twoButtonDialog) let { classes } = options;
return null;
if(dontShowTheDialog(dontShowAgain)) { if (!popupEnabled || twoButtonDialog) {
// Maybe we should pass some parameters here? I'm not sure
// and currently we don't need any parameters.
submitFunction();
return null; return null;
} }
var buttons = []; if (dontShowTheDialog(dontShowAgain)) {
// Maybe we should pass some parameters here? I'm not sure
// and currently we don't need any parameters.
submitFunction();
var leftButton = leftButtonKey ? return null;
APP.translation.generateTranslationHTML(leftButtonKey) : }
APP.translation.generateTranslationHTML('dialog.Submit');
buttons.push({ title: leftButton, value: true});
var cancelButton const buttons = [];
= APP.translation.generateTranslationHTML("dialog.Cancel");
buttons.push({title: cancelButton, value: false}); const leftButton = leftButtonKey
? APP.translation.generateTranslationHTML(leftButtonKey)
: APP.translation.generateTranslationHTML('dialog.Submit');
buttons.push({ title: leftButton,
value: true });
const cancelButton
= APP.translation.generateTranslationHTML('dialog.Cancel');
buttons.push({ title: cancelButton,
value: false });
let message = msgString;
var message = msgString;
if (msgKey) { if (msgKey) {
message = APP.translation.generateTranslationHTML(msgKey); message = APP.translation.generateTranslationHTML(msgKey);
} }
@ -220,20 +241,20 @@ var messageHandler = {
twoButtonDialog = $.prompt(message, { twoButtonDialog = $.prompt(message, {
title: this._getFormattedTitleString(titleKey), title: this._getFormattedTitleString(titleKey),
persistent: false, persistent: false,
buttons: buttons, buttons,
defaultButton: defaultButton, defaultButton,
focus: focus, focus,
loaded: loadedFunction, loaded: loadedFunction,
promptspeed: 0, promptspeed: 0,
classes, classes,
submit: dontShowAgainSubmitFunctionWrapper(dontShowAgain, submit: dontShowAgainSubmitFunctionWrapper(dontShowAgain,
function (e, v, m, f) { (e, v, m, f) => { // eslint-disable-line max-params
twoButtonDialog = null; twoButtonDialog = null;
if (v && submitFunction) { if (v && submitFunction) {
submitFunction(e, v, m, f); submitFunction(e, v, m, f);
} }
}), }),
close: function (e, v, m, f) { close(e, v, m, f) { // eslint-disable-line max-params
twoButtonDialog = null; twoButtonDialog = null;
if (closeFunction) { if (closeFunction) {
closeFunction(e, v, m, f); closeFunction(e, v, m, f);
@ -241,6 +262,7 @@ var messageHandler = {
} }
}); });
APP.translation.translateElement(twoButtonDialog); APP.translation.translateElement(twoButtonDialog);
return $.prompt.getApi(); return $.prompt.getApi();
}, },
@ -270,7 +292,7 @@ var messageHandler = {
* @param {string} dontShowAgain.localStorageKey the key for the local * @param {string} dontShowAgain.localStorageKey the key for the local
* storage. if not provided dontShowAgain.id will be used. * storage. if not provided dontShowAgain.id will be used.
*/ */
openDialog( openDialog(// eslint-disable-line max-params
titleKey, titleKey,
msgString, msgString,
persistent, persistent,
@ -279,29 +301,33 @@ var messageHandler = {
loadedFunction, loadedFunction,
closeFunction, closeFunction,
dontShowAgain) { dontShowAgain) {
if (!popupEnabled) if (!popupEnabled) {
return;
if(dontShowTheDialog(dontShowAgain)) {
// Maybe we should pass some parameters here? I'm not sure
// and currently we don't need any parameters.
submitFunction();
return; return;
} }
let args = { if (dontShowTheDialog(dontShowAgain)) {
// Maybe we should pass some parameters here? I'm not sure
// and currently we don't need any parameters.
submitFunction();
return;
}
const args = {
title: this._getFormattedTitleString(titleKey), title: this._getFormattedTitleString(titleKey),
persistent: persistent, persistent,
buttons: buttons, buttons,
defaultButton: 1, defaultButton: 1,
promptspeed: 0, promptspeed: 0,
loaded: function() { loaded() {
if (loadedFunction) { if (loadedFunction) {
// eslint-disable-next-line prefer-rest-params
loadedFunction.apply(this, arguments); loadedFunction.apply(this, arguments);
} }
// Hide the close button // Hide the close button
if (persistent) { if (persistent) {
$(".jqiclose", this).hide(); $('.jqiclose', this).hide();
} }
}, },
submit: dontShowAgainSubmitFunctionWrapper( submit: dontShowAgainSubmitFunctionWrapper(
@ -314,9 +340,11 @@ var messageHandler = {
args.closeText = ''; args.closeText = '';
} }
let dialog = $.prompt( const dialog = $.prompt(
msgString + generateDontShowCheckbox(dontShowAgain), args); msgString + generateDontShowCheckbox(dontShowAgain), args);
APP.translation.translateElement(dialog); APP.translation.translateElement(dialog);
return $.prompt.getApi(); return $.prompt.getApi();
}, },
@ -326,10 +354,13 @@ var messageHandler = {
* @return the title string formatted as a div. * @return the title string formatted as a div.
*/ */
_getFormattedTitleString(titleKey) { _getFormattedTitleString(titleKey) {
let $titleString = $('<h2>'); const $titleString = $('<h2>');
$titleString.addClass('aui-dialog2-header-main'); $titleString.addClass('aui-dialog2-header-main');
$titleString.attr('data-i18n',titleKey); $titleString.attr('data-i18n', titleKey);
return $('<div>').append($titleString).html();
return $('<div>').append($titleString)
.html();
}, },
/** /**
@ -359,23 +390,28 @@ var messageHandler = {
* @param options impromptu options * @param options impromptu options
* @param translateOptions options passed to translation * @param translateOptions options passed to translation
*/ */
openDialogWithStates: function (statesObject, options, translateOptions) { openDialogWithStates(statesObject, options, translateOptions) {
if (!popupEnabled) if (!popupEnabled) {
return; return;
let { classes, size } = options; }
let defaultClasses = this._getDialogClasses(size); const { classes, size } = options;
const defaultClasses = this._getDialogClasses(size);
options.classes = Object.assign({}, defaultClasses, classes); options.classes = Object.assign({}, defaultClasses, classes);
options.promptspeed = options.promptspeed || 0; options.promptspeed = options.promptspeed || 0;
for (let state in statesObject) { for (const state in statesObject) { // eslint-disable-line guard-for-in
let currentState = statesObject[state]; const currentState = statesObject[state];
if(currentState.titleKey) {
if (currentState.titleKey) {
currentState.title currentState.title
= this._getFormattedTitleString(currentState.titleKey); = this._getFormattedTitleString(currentState.titleKey);
} }
} }
let dialog = $.prompt(statesObject, options); const dialog = $.prompt(statesObject, options);
APP.translation.translateElement(dialog, translateOptions); APP.translation.translateElement(dialog, translateOptions);
return $.prompt.getApi(); return $.prompt.getApi();
}, },
@ -392,17 +428,20 @@ var messageHandler = {
* @returns {object} popup window object if opened successfully or undefined * @returns {object} popup window object if opened successfully or undefined
* in case we failed to open it(popup blocked) * in case we failed to open it(popup blocked)
*/ */
openCenteredPopup: function (url, w, h, onPopupClosed) { // eslint-disable-next-line max-params
if (!popupEnabled) openCenteredPopup(url, w, h, onPopupClosed) {
if (!popupEnabled) {
return; return;
}
var l = window.screenX + (window.innerWidth / 2) - (w / 2); const l = window.screenX + (window.innerWidth / 2) - (w / 2);
var t = window.screenY + (window.innerHeight / 2) - (h / 2); const t = window.screenY + (window.innerHeight / 2) - (h / 2);
var popup = window.open( const popup = window.open(
url, '_blank', url, '_blank',
'top=' + t + ', left=' + l + ', width=' + w + ', height=' + h + ''); String(`top=${t}, left=${l}, width=${w}, height=${h}`));
if (popup && onPopupClosed) { if (popup && onPopupClosed) {
var pollTimer = window.setInterval(function () { const pollTimer = window.setInterval(() => {
if (popup.closed !== false) { if (popup.closed !== false) {
window.clearInterval(pollTimer); window.clearInterval(pollTimer);
onPopupClosed(); onPopupClosed();
@ -420,10 +459,11 @@ var messageHandler = {
* @param msgKey the text of the message * @param msgKey the text of the message
* @param error the error that is being reported * @param error the error that is being reported
*/ */
openReportDialog: function(titleKey, msgKey, error) { openReportDialog(titleKey, msgKey, error) {
this.openMessageDialog(titleKey, msgKey); this.openMessageDialog(titleKey, msgKey);
logger.log(error); logger.log(error);
//FIXME send the error to the server
// FIXME send the error to the server
}, },
/** /**
@ -431,14 +471,7 @@ var messageHandler = {
* @param titleKey the title of the message. * @param titleKey the title of the message.
* @param msgKey the text of the message. * @param msgKey the text of the message.
*/ */
showError: function(titleKey, msgKey) { showError(titleKey = 'dialog.oops', msgKey = 'dialog.defaultError') {
if (!titleKey) {
titleKey = "dialog.oops";
}
if (!msgKey) {
msgKey = "dialog.defaultError";
}
messageHandler.openMessageDialog(titleKey, msgKey); messageHandler.openMessageDialog(titleKey, msgKey);
}, },
@ -454,7 +487,7 @@ var messageHandler = {
* @param messageArguments object with the arguments for the message. * @param messageArguments object with the arguments for the message.
* @param optional configurations for the notification (e.g. timeout) * @param optional configurations for the notification (e.g. timeout)
*/ */
participantNotification( participantNotification( // eslint-disable-line max-params
displayName, displayName,
displayNameKey, displayNameKey,
cls, cls,
@ -484,12 +517,12 @@ var messageHandler = {
* translation. * translation.
* @returns {void} * @returns {void}
*/ */
notify: function(titleKey, messageKey, messageArguments) { notify(titleKey, messageKey, messageArguments) {
this.participantNotification( this.participantNotification(
null, titleKey, null, messageKey, messageArguments); null, titleKey, null, messageKey, messageArguments);
}, },
enablePopups: function (enable) { enablePopups(enable) {
popupEnabled = enable; popupEnabled = enable;
}, },
@ -498,8 +531,8 @@ var messageHandler = {
* false otherwise * false otherwise
* @returns {boolean} isOpened * @returns {boolean} isOpened
*/ */
isDialogOpened: function () { isDialogOpened() {
return !!$.prompt.getCurrentStateName(); return Boolean($.prompt.getCurrentStateName());
} }
}; };

View File

@ -31,7 +31,7 @@ const IndicatorFontSizes = {
/** /**
* Created by hristo on 12/22/14. * Created by hristo on 12/22/14.
*/ */
var UIUtil = { const UIUtil = {
/** /**
* Returns the available video width. * Returns the available video width.
@ -43,17 +43,18 @@ var UIUtil = {
/** /**
* Changes the style class of the element given by id. * Changes the style class of the element given by id.
*/ */
buttonClick: function(id, classname) { buttonClick(id, classname) {
// add the class to the clicked element // add the class to the clicked element
$("#" + id).toggleClass(classname); $(`#${id}`).toggleClass(classname);
}, },
/** /**
* Returns the text width for the given element. * Returns the text width for the given element.
* *
* @param el the element * @param el the element
*/ */
getTextWidth(el) { getTextWidth(el) {
return (el.clientWidth + 1); return el.clientWidth + 1;
}, },
/** /**
@ -62,7 +63,7 @@ var UIUtil = {
* @param el the element * @param el the element
*/ */
getTextHeight(el) { getTextHeight(el) {
return (el.clientHeight + 1); return el.clientHeight + 1;
}, },
/** /**
@ -78,7 +79,8 @@ var UIUtil = {
* Escapes the given text. * Escapes the given text.
*/ */
escapeHtml(unsafeText) { escapeHtml(unsafeText) {
return $('<div/>').text(unsafeText).html(); return $('<div/>').text(unsafeText)
.html();
}, },
/** /**
@ -88,22 +90,27 @@ var UIUtil = {
* @returns {string} unescaped html string. * @returns {string} unescaped html string.
*/ */
unescapeHtml(safe) { unescapeHtml(safe) {
return $('<div />').html(safe).text(); return $('<div />').html(safe)
.text();
}, },
imageToGrayScale(canvas) { imageToGrayScale(canvas) {
var context = canvas.getContext('2d'); const context = canvas.getContext('2d');
var imgData = context.getImageData(0, 0, canvas.width, canvas.height); const imgData = context.getImageData(0, 0, canvas.width, canvas.height);
var pixels = imgData.data; const pixels = imgData.data;
for (var i = 0, n = pixels.length; i < n; i += 4) { for (let i = 0, n = pixels.length; i < n; i += 4) {
var grayscale const grayscale
= pixels[i] * 0.3 + pixels[i+1] * 0.59 + pixels[i+2] * 0.11; = (pixels[i] * 0.3)
pixels[i ] = grayscale; // red + (pixels[i + 1] * 0.59)
pixels[i+1] = grayscale; // green + (pixels[i + 2] * 0.11);
pixels[i+2] = grayscale; // blue
pixels[i] = grayscale; // red
pixels[i + 1] = grayscale; // green
pixels[i + 2] = grayscale; // blue
// pixels[i+3] is alpha // pixels[i+3] is alpha
} }
// redraw the image in black & white // redraw the image in black & white
context.putImageData(imgData, 0, 0); context.putImageData(imgData, 0, 0);
}, },
@ -114,7 +121,8 @@ var UIUtil = {
* @param newChild the new element that will be inserted into the container * @param newChild the new element that will be inserted into the container
*/ */
prependChild(container, newChild) { prependChild(container, newChild) {
var firstChild = container.childNodes[0]; const firstChild = container.childNodes[0];
if (firstChild) { if (firstChild) {
container.insertBefore(newChild, firstChild); container.insertBefore(newChild, firstChild);
} else { } else {
@ -152,6 +160,7 @@ var UIUtil = {
*/ */
setVisible(id, visible) { setVisible(id, visible) {
let element; let element;
if (id instanceof HTMLElement) { if (id instanceof HTMLElement) {
element = id; element = id;
} else { } else {
@ -162,20 +171,20 @@ var UIUtil = {
return; return;
} }
if (!visible) if (!visible) {
element.classList.add('hide'); element.classList.add('hide');
else if (element.classList.contains('hide')) { } else if (element.classList.contains('hide')) {
element.classList.remove('hide'); element.classList.remove('hide');
} }
let type = this._getElementDefaultDisplay(element.tagName); const type = this._getElementDefaultDisplay(element.tagName);
let className = SHOW_CLASSES[type]; const className = SHOW_CLASSES[type];
if (visible) { if (visible) {
element.classList.add(className); element.classList.add(className);
} } else if (element.classList.contains(className)) {
else if (element.classList.contains(className))
element.classList.remove(className); element.classList.remove(className);
}
}, },
/** /**
@ -185,10 +194,11 @@ var UIUtil = {
* @private * @private
*/ */
_getElementDefaultDisplay(tag) { _getElementDefaultDisplay(tag) {
let tempElement = document.createElement(tag); const tempElement = document.createElement(tag);
document.body.appendChild(tempElement); document.body.appendChild(tempElement);
let style = window.getComputedStyle(tempElement).display; const style = window.getComputedStyle(tempElement).display;
document.body.removeChild(tempElement); document.body.removeChild(tempElement);
return style; return style;
@ -203,7 +213,7 @@ var UIUtil = {
*/ */
setVisibleBySelector(jquerySelector, isVisible) { setVisibleBySelector(jquerySelector, isVisible) {
if (jquerySelector && jquerySelector.length > 0) { if (jquerySelector && jquerySelector.length > 0) {
jquerySelector.css("visibility", isVisible ? "visible" : "hidden"); jquerySelector.css('visibility', isVisible ? 'visible' : 'hidden');
} }
}, },
@ -264,7 +274,8 @@ var UIUtil = {
*/ */
attrsToString(attrs) { attrsToString(attrs) {
return ( return (
Object.keys(attrs).map(key => ` ${key}="${attrs[key]}"`).join(' ')); Object.keys(attrs).map(key => ` ${key}="${attrs[key]}"`)
.join(' '));
}, },
/** /**
@ -275,7 +286,7 @@ var UIUtil = {
* @param {el} The DOM element we'd like to check for visibility * @param {el} The DOM element we'd like to check for visibility
*/ */
isVisible(el) { isVisible(el) {
return (el.offsetParent !== null); return el.offsetParent !== null;
}, },
/** /**
@ -288,26 +299,33 @@ var UIUtil = {
* element * element
*/ */
animateShowElement(selector, show, hideDelay) { animateShowElement(selector, show, hideDelay) {
if(show) { if (show) {
if (!selector.is(":visible")) if (!selector.is(':visible')) {
selector.css("display", "inline-block"); selector.css('display', 'inline-block');
}
selector.fadeIn(300, selector.fadeIn(300,
() => {selector.css({opacity: 1});} () => {
selector.css({ opacity: 1 });
}
); );
if (hideDelay && hideDelay > 0) if (hideDelay && hideDelay > 0) {
setTimeout( setTimeout(
() => { () => {
selector.fadeOut( selector.fadeOut(
300, 300,
() => { selector.css({opacity: 0}); }); () => {
selector.css({ opacity: 0 });
});
}, },
hideDelay); hideDelay);
} }
else { } else {
selector.fadeOut(300, selector.fadeOut(300,
() => {selector.css({opacity: 0});} () => {
selector.css({ opacity: 0 });
}
); );
} }
}, },
@ -318,7 +336,7 @@ var UIUtil = {
* @param cssValue the string value we obtain when querying css properties * @param cssValue the string value we obtain when querying css properties
*/ */
parseCssInt(cssValue) { parseCssInt(cssValue) {
return parseInt(cssValue) || 0; return parseInt(cssValue, 10) || 0;
}, },
/** /**
@ -332,8 +350,8 @@ var UIUtil = {
aLinkElement.attr('href', link); aLinkElement.attr('href', link);
} else { } else {
aLinkElement.css({ aLinkElement.css({
"pointer-events": "none", 'pointer-events': 'none',
"cursor": "default" 'cursor': 'default'
}); });
} }
}, },

View File

@ -2,8 +2,8 @@
import { setFilmstripVisibility } from '../../../react/features/filmstrip'; import { setFilmstripVisibility } from '../../../react/features/filmstrip';
import UIEvents from "../../../service/UI/UIEvents"; import UIEvents from '../../../service/UI/UIEvents';
import UIUtil from "../util/UIUtil"; import UIUtil from '../util/UIUtil';
import { sendEvent } from '../../../react/features/analytics'; import { sendEvent } from '../../../react/features/analytics';
@ -13,7 +13,7 @@ const Filmstrip = {
* @param eventEmitter the {EventEmitter} through which {Filmstrip} is to * @param eventEmitter the {EventEmitter} through which {Filmstrip} is to
* emit/fire {UIEvents} (such as {UIEvents.TOGGLED_FILMSTRIP}). * emit/fire {UIEvents} (such as {UIEvents.TOGGLED_FILMSTRIP}).
*/ */
init (eventEmitter) { init(eventEmitter) {
this.iconMenuDownClassName = 'icon-menu-down'; this.iconMenuDownClassName = 'icon-menu-down';
this.iconMenuUpClassName = 'icon-menu-up'; this.iconMenuUpClassName = 'icon-menu-up';
this.filmstripContainerClassName = 'filmstrip'; this.filmstripContainerClassName = 'filmstrip';
@ -33,13 +33,14 @@ const Filmstrip = {
* Initializes the filmstrip toolbar. * Initializes the filmstrip toolbar.
*/ */
_initFilmstripToolbar() { _initFilmstripToolbar() {
let toolbarContainerHTML = this._generateToolbarHTML(); const toolbarContainerHTML = this._generateToolbarHTML();
let className = this.filmstripContainerClassName; const className = this.filmstripContainerClassName;
let container = document.querySelector(`.${className}`); const container = document.querySelector(`.${className}`);
UIUtil.prependChild(container, toolbarContainerHTML); UIUtil.prependChild(container, toolbarContainerHTML);
let iconSelector = '#toggleFilmstripButton i'; const iconSelector = '#toggleFilmstripButton i';
this.toggleFilmstripIcon = document.querySelector(iconSelector); this.toggleFilmstripIcon = document.querySelector(iconSelector);
}, },
@ -49,8 +50,9 @@ const Filmstrip = {
* @private * @private
*/ */
_generateToolbarHTML() { _generateToolbarHTML() {
let container = document.createElement('div'); const container = document.createElement('div');
let isVisible = this.isFilmstripVisible(); const isVisible = this.isFilmstripVisible();
container.className = 'filmstrip__toolbar'; container.className = 'filmstrip__toolbar';
container.innerHTML = ` container.innerHTML = `
<button id="toggleFilmstripButton"> <button id="toggleFilmstripButton">
@ -81,14 +83,15 @@ const Filmstrip = {
* @private * @private
*/ */
_registerToggleFilmstripShortcut() { _registerToggleFilmstripShortcut() {
let shortcut = 'F'; const shortcut = 'F';
let shortcutAttr = 'filmstripPopover'; const shortcutAttr = 'filmstripPopover';
let description = 'keyboardShortcuts.toggleFilmstrip'; const description = 'keyboardShortcuts.toggleFilmstrip';
// Important: // Important:
// Firing the event instead of executing toggleFilmstrip method because // Firing the event instead of executing toggleFilmstrip method because
// it's important to hide the filmstrip by UI.toggleFilmstrip in order // it's important to hide the filmstrip by UI.toggleFilmstrip in order
// to correctly resize the video area. // to correctly resize the video area.
let handler = () => this.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP); const handler = () => this.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP);
APP.keyboardshortcut.registerShortcut( APP.keyboardshortcut.registerShortcut(
shortcut, shortcut,
@ -102,8 +105,9 @@ const Filmstrip = {
* Changes classes of icon for showing down state * Changes classes of icon for showing down state
*/ */
showMenuDownIcon() { showMenuDownIcon() {
let icon = this.toggleFilmstripIcon; const icon = this.toggleFilmstripIcon;
if(icon) {
if (icon) {
icon.classList.add(this.iconMenuDownClassName); icon.classList.add(this.iconMenuDownClassName);
icon.classList.remove(this.iconMenuUpClassName); icon.classList.remove(this.iconMenuUpClassName);
} }
@ -113,8 +117,9 @@ const Filmstrip = {
* Changes classes of icon for showing up state * Changes classes of icon for showing up state
*/ */
showMenuUpIcon() { showMenuUpIcon() {
let icon = this.toggleFilmstripIcon; const icon = this.toggleFilmstripIcon;
if(icon) {
if (icon) {
icon.classList.add(this.iconMenuUpClassName); icon.classList.add(this.iconMenuUpClassName);
icon.classList.remove(this.iconMenuDownClassName); icon.classList.remove(this.iconMenuDownClassName);
} }
@ -137,7 +142,9 @@ const Filmstrip = {
*/ */
toggleFilmstrip(visible, sendAnalytics = true) { toggleFilmstrip(visible, sendAnalytics = true) {
const isVisibleDefined = typeof visible === 'boolean'; const isVisibleDefined = typeof visible === 'boolean';
if (!isVisibleDefined) { if (!isVisibleDefined) {
// eslint-disable-next-line no-param-reassign
visible = this.isFilmstripVisible(); visible = this.isFilmstripVisible();
} else if (this.isFilmstripVisible() === visible) { } else if (this.isFilmstripVisible() === visible) {
return; return;
@ -145,7 +152,7 @@ const Filmstrip = {
if (sendAnalytics) { if (sendAnalytics) {
sendEvent('toolbar.filmstrip.toggled'); sendEvent('toolbar.filmstrip.toggled');
} }
this.filmstrip.toggleClass("hidden"); this.filmstrip.toggleClass('hidden');
if (visible) { if (visible) {
this.showMenuUpIcon(); this.showMenuUpIcon();
@ -190,9 +197,10 @@ const Filmstrip = {
// display should be. // display should be.
if (this.isFilmstripVisible() && !interfaceConfig.VERTICAL_FILMSTRIP) { if (this.isFilmstripVisible() && !interfaceConfig.VERTICAL_FILMSTRIP) {
return $(`.${this.filmstripContainerClassName}`).outerHeight(); return $(`.${this.filmstripContainerClassName}`).outerHeight();
} else {
return 0;
} }
return 0;
}, },
/** /**
@ -200,9 +208,9 @@ const Filmstrip = {
* @returns {*|{localVideo, remoteVideo}} * @returns {*|{localVideo, remoteVideo}}
*/ */
calculateThumbnailSize() { calculateThumbnailSize() {
let availableSizes = this.calculateAvailableSize(); const availableSizes = this.calculateAvailableSize();
let width = availableSizes.availableWidth; const width = availableSizes.availableWidth;
let height = availableSizes.availableHeight; const height = availableSizes.availableHeight;
return this.calculateThumbnailSizeFromAvailable(width, height); return this.calculateThumbnailSizeFromAvailable(width, height);
}, },
@ -215,17 +223,17 @@ const Filmstrip = {
*/ */
calculateAvailableSize() { calculateAvailableSize() {
let availableHeight = interfaceConfig.FILM_STRIP_MAX_HEIGHT; let availableHeight = interfaceConfig.FILM_STRIP_MAX_HEIGHT;
let thumbs = this.getThumbs(true); const thumbs = this.getThumbs(true);
let numvids = thumbs.remoteThumbs.length; const numvids = thumbs.remoteThumbs.length;
let localVideoContainer = $("#localVideoContainer"); const localVideoContainer = $('#localVideoContainer');
/** /**
* If the videoAreaAvailableWidth is set we use this one to calculate * If the videoAreaAvailableWidth is set we use this one to calculate
* the filmstrip width, because we're probably in a state where the * the filmstrip width, because we're probably in a state where the
* filmstrip size hasn't been updated yet, but it will be. * filmstrip size hasn't been updated yet, but it will be.
*/ */
let videoAreaAvailableWidth const videoAreaAvailableWidth
= UIUtil.getAvailableVideoWidth() = UIUtil.getAvailableVideoWidth()
- this._getFilmstripExtraPanelsWidth() - this._getFilmstripExtraPanelsWidth()
- UIUtil.parseCssInt(this.filmstrip.css('right'), 10) - UIUtil.parseCssInt(this.filmstrip.css('right'), 10)
@ -238,9 +246,9 @@ const Filmstrip = {
let availableWidth = videoAreaAvailableWidth; let availableWidth = videoAreaAvailableWidth;
// If local thumb is not hidden // If local thumb is not hidden
if(thumbs.localThumb) { if (thumbs.localThumb) {
availableWidth = Math.floor( availableWidth = Math.floor(
(videoAreaAvailableWidth - ( videoAreaAvailableWidth - (
UIUtil.parseCssInt( UIUtil.parseCssInt(
localVideoContainer.css('borderLeftWidth'), 10) localVideoContainer.css('borderLeftWidth'), 10)
+ UIUtil.parseCssInt( + UIUtil.parseCssInt(
@ -252,7 +260,7 @@ const Filmstrip = {
+ UIUtil.parseCssInt( + UIUtil.parseCssInt(
localVideoContainer.css('marginLeft'), 10) localVideoContainer.css('marginLeft'), 10)
+ UIUtil.parseCssInt( + UIUtil.parseCssInt(
localVideoContainer.css('marginRight'), 10))) localVideoContainer.css('marginRight'), 10))
); );
} }
@ -260,9 +268,10 @@ const Filmstrip = {
// filmstrip mode we don't need to calculate further any adjustments // filmstrip mode we don't need to calculate further any adjustments
// to width based on the number of videos present. // to width based on the number of videos present.
if (numvids && !interfaceConfig.VERTICAL_FILMSTRIP) { if (numvids && !interfaceConfig.VERTICAL_FILMSTRIP) {
let remoteVideoContainer = thumbs.remoteThumbs.eq(0); const remoteVideoContainer = thumbs.remoteThumbs.eq(0);
availableWidth = Math.floor( availableWidth = Math.floor(
(videoAreaAvailableWidth - numvids * ( videoAreaAvailableWidth - (numvids * (
UIUtil.parseCssInt( UIUtil.parseCssInt(
remoteVideoContainer.css('borderLeftWidth'), 10) remoteVideoContainer.css('borderLeftWidth'), 10)
+ UIUtil.parseCssInt( + UIUtil.parseCssInt(
@ -278,7 +287,8 @@ const Filmstrip = {
); );
} }
let maxHeight const maxHeight
// If the MAX_HEIGHT property hasn't been specified // If the MAX_HEIGHT property hasn't been specified
// we have the static value. // we have the static value.
= Math.min(interfaceConfig.FILM_STRIP_MAX_HEIGHT || 120, = Math.min(interfaceConfig.FILM_STRIP_MAX_HEIGHT || 120,
@ -287,7 +297,8 @@ const Filmstrip = {
availableHeight availableHeight
= Math.min(maxHeight, window.innerHeight - 18); = Math.min(maxHeight, window.innerHeight - 18);
return { availableWidth, availableHeight }; return { availableWidth,
availableHeight };
}, },
/** /**
@ -300,15 +311,19 @@ const Filmstrip = {
* @private * @private
*/ */
_getFilmstripExtraPanelsWidth() { _getFilmstripExtraPanelsWidth() {
let className = this.filmstripContainerClassName; const className = this.filmstripContainerClassName;
let width = 0; let width = 0;
$(`.${className}`) $(`.${className}`)
.children() .children()
.each(function () { .each(function() {
/* eslint-disable no-invalid-this */
if (this.id !== 'remoteVideos') { if (this.id !== 'remoteVideos') {
width += $(this).outerWidth(); width += $(this).outerWidth();
} }
/* eslint-enable no-invalid-this */
}); });
return width; return width;
}, },
@ -362,16 +377,17 @@ const Filmstrip = {
const remoteThumbsInRow = interfaceConfig.VERTICAL_FILMSTRIP const remoteThumbsInRow = interfaceConfig.VERTICAL_FILMSTRIP
? 0 : this.getThumbs(true).remoteThumbs.length; ? 0 : this.getThumbs(true).remoteThumbs.length;
const remoteLocalWidthRatio = interfaceConfig.REMOTE_THUMBNAIL_RATIO / const remoteLocalWidthRatio = interfaceConfig.REMOTE_THUMBNAIL_RATIO
interfaceConfig.LOCAL_THUMBNAIL_RATIO; / interfaceConfig.LOCAL_THUMBNAIL_RATIO;
const lW = Math.min(availableWidth / const lW = Math.min(availableWidth
(remoteLocalWidthRatio * remoteThumbsInRow + 1), availableHeight * / ((remoteLocalWidthRatio * remoteThumbsInRow) + 1), availableHeight
interfaceConfig.LOCAL_THUMBNAIL_RATIO); * interfaceConfig.LOCAL_THUMBNAIL_RATIO);
const h = lW / interfaceConfig.LOCAL_THUMBNAIL_RATIO; const h = lW / interfaceConfig.LOCAL_THUMBNAIL_RATIO;
const remoteVideoWidth = lW * remoteLocalWidthRatio; const remoteVideoWidth = lW * remoteLocalWidthRatio;
let localVideo; let localVideo;
if (interfaceConfig.VERTICAL_FILMSTRIP) { if (interfaceConfig.VERTICAL_FILMSTRIP) {
localVideo = { localVideo = {
thumbWidth: remoteVideoWidth, thumbWidth: remoteVideoWidth,
@ -401,28 +417,32 @@ const Filmstrip = {
* @param forceUpdate * @param forceUpdate
* @returns {Promise} * @returns {Promise}
*/ */
// eslint-disable-next-line max-params
resizeThumbnails(local, remote, animate = false, forceUpdate = false) { resizeThumbnails(local, remote, animate = false, forceUpdate = false) {
return new Promise(resolve => { return new Promise(resolve => {
let thumbs = this.getThumbs(!forceUpdate); const thumbs = this.getThumbs(!forceUpdate);
let promises = []; const promises = [];
if(thumbs.localThumb) { if (thumbs.localThumb) {
promises.push(new Promise((resolve) => { // eslint-disable-next-line no-shadow
promises.push(new Promise(resolve => {
thumbs.localThumb.animate({ thumbs.localThumb.animate({
height: local.thumbHeight, height: local.thumbHeight,
width: local.thumbWidth width: local.thumbWidth
}, this._getAnimateOptions(animate, resolve)); }, this._getAnimateOptions(animate, resolve));
})); }));
} }
if(thumbs.remoteThumbs) { if (thumbs.remoteThumbs) {
promises.push(new Promise((resolve) => { // eslint-disable-next-line no-shadow
promises.push(new Promise(resolve => {
thumbs.remoteThumbs.animate({ thumbs.remoteThumbs.animate({
height: remote.thumbHeight, height: remote.thumbHeight,
width: remote.thumbWidth width: remote.thumbWidth
}, this._getAnimateOptions(animate, resolve)); }, this._getAnimateOptions(animate, resolve));
})); }));
} }
promises.push(new Promise((resolve) => { // eslint-disable-next-line no-shadow
promises.push(new Promise(resolve => {
// Let CSS take care of height in vertical filmstrip mode. // Let CSS take care of height in vertical filmstrip mode.
if (interfaceConfig.VERTICAL_FILMSTRIP) { if (interfaceConfig.VERTICAL_FILMSTRIP) {
$('#filmstripLocalVideo').animate({ $('#filmstripLocalVideo').animate({
@ -438,9 +458,10 @@ const Filmstrip = {
})); }));
promises.push(new Promise(() => { promises.push(new Promise(() => {
let { localThumb } = this.getThumbs(); const { localThumb } = this.getThumbs();
let height = localThumb ? localThumb.height() : 0; const height = localThumb ? localThumb.height() : 0;
let fontSize = UIUtil.getIndicatorFontSize(height); const fontSize = UIUtil.getIndicatorFontSize(height);
this.filmstrip.find('.indicator').animate({ this.filmstrip.find('.indicator').animate({
fontSize fontSize
}, this._getAnimateOptions(animate, resolve)); }, this._getAnimateOptions(animate, resolve));
@ -471,24 +492,27 @@ const Filmstrip = {
/** /**
* Returns thumbnails of the filmstrip * Returns thumbnails of the filmstrip
* @param only_visible * @param onlyVisible
* @returns {object} thumbnails * @returns {object} thumbnails
*/ */
getThumbs(only_visible = false) { getThumbs(onlyVisible = false) {
let selector = 'span'; let selector = 'span';
if (only_visible) {
if (onlyVisible) {
selector += ':visible'; selector += ':visible';
} }
let localThumb = $("#localVideoContainer"); const localThumb = $('#localVideoContainer');
let remoteThumbs = this.filmstripRemoteVideos.children(selector); const remoteThumbs = this.filmstripRemoteVideos.children(selector);
// Exclude the local video container if it has been hidden. // Exclude the local video container if it has been hidden.
if (localThumb.hasClass("hidden")) { if (localThumb.hasClass('hidden')) {
return { remoteThumbs }; return { remoteThumbs };
} else {
return { remoteThumbs, localThumb };
} }
return { remoteThumbs,
localThumb };
} }
}; };

View File

@ -3,19 +3,19 @@
* Base class for all Large containers which we can show. * Base class for all Large containers which we can show.
*/ */
export default class LargeContainer { export default class LargeContainer {
/* eslint-disable no-unused-vars, no-empty-function */
/** /**
* Show this container. * Show this container.
* @returns Promise * @returns Promise
*/ */
show () { show() {
} }
/** /**
* Hide this container. * Hide this container.
* @returns Promise * @returns Promise
*/ */
hide () { hide() {
} }
/** /**
@ -24,20 +24,19 @@ export default class LargeContainer {
* @param {number} containerHeight available height * @param {number} containerHeight available height
* @param {boolean} animate if container should animate it's resize process * @param {boolean} animate if container should animate it's resize process
*/ */
// eslint-disable-next-line no-unused-vars resize(containerWidth, containerHeight, animate) {
resize (containerWidth, containerHeight, animate) {
} }
/** /**
* Handler for "hover in" events. * Handler for "hover in" events.
*/ */
onHoverIn (e) { // eslint-disable-line no-unused-vars onHoverIn(e) {
} }
/** /**
* Handler for "hover out" events. * Handler for "hover out" events.
*/ */
onHoverOut (e) { // eslint-disable-line no-unused-vars onHoverOut(e) {
} }
/** /**
@ -46,14 +45,14 @@ export default class LargeContainer {
* @param {JitsiTrack?} stream new stream * @param {JitsiTrack?} stream new stream
* @param {string} videoType video type * @param {string} videoType video type
*/ */
setStream (userID, stream, videoType) {// eslint-disable-line no-unused-vars setStream(userID, stream, videoType) {
} }
/** /**
* Show or hide user avatar. * Show or hide user avatar.
* @param {boolean} show * @param {boolean} show
*/ */
showAvatar (show) { // eslint-disable-line no-unused-vars showAvatar(show) {
} }
/** /**
@ -61,6 +60,8 @@ export default class LargeContainer {
* when the container is on stage. * when the container is on stage.
* @return {boolean} * @return {boolean}
*/ */
stayOnStage () { stayOnStage() {
} }
/* eslint-enable no-unused-vars, no-empty-function */
} }

View File

@ -7,7 +7,7 @@ import { Provider } from 'react-redux';
import { PresenceLabel } from '../../../react/features/presence-status'; import { PresenceLabel } from '../../../react/features/presence-status';
/* eslint-enable no-unused-vars */ /* eslint-enable no-unused-vars */
const logger = require("jitsi-meet-logger").getLogger(__filename); const logger = require('jitsi-meet-logger').getLogger(__filename);
import { import {
JitsiParticipantConnectionStatus JitsiParticipantConnectionStatus
@ -16,15 +16,16 @@ import {
updateKnownLargeVideoResolution updateKnownLargeVideoResolution
} from '../../../react/features/large-video'; } from '../../../react/features/large-video';
import Avatar from "../avatar/Avatar"; import Avatar from '../avatar/Avatar';
import {createDeferred} from '../../util/helpers'; import { createDeferred } from '../../util/helpers';
import UIEvents from "../../../service/UI/UIEvents"; import UIEvents from '../../../service/UI/UIEvents';
import UIUtil from "../util/UIUtil"; import UIUtil from '../util/UIUtil';
import {VideoContainer, VIDEO_CONTAINER_TYPE} from "./VideoContainer"; import { VideoContainer, VIDEO_CONTAINER_TYPE } from './VideoContainer';
import AudioLevels from "../audio_levels/AudioLevels"; import AudioLevels from '../audio_levels/AudioLevels';
const DESKTOP_CONTAINER_TYPE = 'desktop'; const DESKTOP_CONTAINER_TYPE = 'desktop';
/** /**
* The time interval in milliseconds to check the video resolution of the video * The time interval in milliseconds to check the video resolution of the video
* being displayed. * being displayed.
@ -49,7 +50,10 @@ export default class LargeVideoManager {
|| containerType === DESKTOP_CONTAINER_TYPE; || containerType === DESKTOP_CONTAINER_TYPE;
} }
constructor (emitter) { /**
*
*/
constructor(emitter) {
/** /**
* The map of <tt>LargeContainer</tt>s where the key is the video * The map of <tt>LargeContainer</tt>s where the key is the video
* container type. * container type.
@ -59,6 +63,7 @@ export default class LargeVideoManager {
this.eventEmitter = emitter; this.eventEmitter = emitter;
this.state = VIDEO_CONTAINER_TYPE; this.state = VIDEO_CONTAINER_TYPE;
// FIXME: We are passing resizeContainer as parameter which is calling // FIXME: We are passing resizeContainer as parameter which is calling
// Container.resize. Probably there's better way to implement this. // Container.resize. Probably there's better way to implement this.
this.videoContainer = new VideoContainer( this.videoContainer = new VideoContainer(
@ -125,7 +130,10 @@ export default class LargeVideoManager {
this.removePresenceLabel(); this.removePresenceLabel();
} }
onHoverIn (e) { /**
*
*/
onHoverIn(e) {
if (!this.state) { if (!this.state) {
return; return;
} }
@ -134,7 +142,10 @@ export default class LargeVideoManager {
container.onHoverIn(e); container.onHoverIn(e);
} }
onHoverOut (e) { /**
*
*/
onHoverOut(e) {
if (!this.state) { if (!this.state) {
return; return;
} }
@ -146,9 +157,10 @@ export default class LargeVideoManager {
/** /**
* Called when the media connection has been interrupted. * Called when the media connection has been interrupted.
*/ */
onVideoInterrupted () { onVideoInterrupted() {
this.enableLocalConnectionProblemFilter(true); this.enableLocalConnectionProblemFilter(true);
this._setLocalConnectionMessage("connection.RECONNECTING"); this._setLocalConnectionMessage('connection.RECONNECTING');
// Show the message only if the video is currently being displayed // Show the message only if the video is currently being displayed
this.showLocalConnectionMessage( this.showLocalConnectionMessage(
LargeVideoManager.isVideoContainer(this.state)); LargeVideoManager.isVideoContainer(this.state));
@ -157,17 +169,25 @@ export default class LargeVideoManager {
/** /**
* Called when the media connection has been restored. * Called when the media connection has been restored.
*/ */
onVideoRestored () { onVideoRestored() {
this.enableLocalConnectionProblemFilter(false); this.enableLocalConnectionProblemFilter(false);
this.showLocalConnectionMessage(false); this.showLocalConnectionMessage(false);
} }
get id () { /**
*
*/
get id() {
const container = this.getCurrentContainer(); const container = this.getCurrentContainer();
return container.id; return container.id;
} }
scheduleLargeVideoUpdate () { /**
*
*/
scheduleLargeVideoUpdate() {
if (this.updateInProcess || !this.newStreamData) { if (this.updateInProcess || !this.newStreamData) {
return; return;
} }
@ -175,6 +195,7 @@ export default class LargeVideoManager {
this.updateInProcess = true; this.updateInProcess = true;
// Include hide()/fadeOut only if we're switching between users // Include hide()/fadeOut only if we're switching between users
// eslint-disable-next-line eqeqeq
const isUserSwitch = this.newStreamData.id != this.id; const isUserSwitch = this.newStreamData.id != this.id;
const container = this.getCurrentContainer(); const container = this.getCurrentContainer();
const preUpdate = isUserSwitch ? container.hide() : Promise.resolve(); const preUpdate = isUserSwitch ? container.hide() : Promise.resolve();
@ -190,9 +211,11 @@ export default class LargeVideoManager {
this.newStreamData = null; this.newStreamData = null;
logger.info("hover in %s", id); logger.info('hover in %s', id);
this.state = videoType; this.state = videoType;
// eslint-disable-next-line no-shadow
const container = this.getCurrentContainer(); const container = this.getCurrentContainer();
container.setStream(id, stream, videoType); container.setStream(id, stream, videoType);
// change the avatar url on large // change the avatar url on large
@ -215,7 +238,7 @@ export default class LargeVideoManager {
=== JitsiParticipantConnectionStatus.ACTIVE === JitsiParticipantConnectionStatus.ACTIVE
|| wasUsersImageCached); || wasUsersImageCached);
let showAvatar const showAvatar
= isVideoContainer = isVideoContainer
&& (APP.conference.isAudioOnly() || !isVideoRenderable); && (APP.conference.isAudioOnly() || !isVideoRenderable);
@ -225,6 +248,7 @@ export default class LargeVideoManager {
// but we still should show watermark // but we still should show watermark
if (showAvatar) { if (showAvatar) {
this.showWatermark(true); this.showWatermark(true);
// If the intention of this switch is to show the avatar // If the intention of this switch is to show the avatar
// we need to make sure that the video is hidden // we need to make sure that the video is hidden
promise = container.hide(); promise = container.hide();
@ -246,10 +270,10 @@ export default class LargeVideoManager {
let messageKey = null; let messageKey = null;
if (isConnectionInterrupted) { if (isConnectionInterrupted) {
messageKey = "connection.USER_CONNECTION_INTERRUPTED"; messageKey = 'connection.USER_CONNECTION_INTERRUPTED';
} else if (connectionStatus } else if (connectionStatus
=== JitsiParticipantConnectionStatus.INACTIVE) { === JitsiParticipantConnectionStatus.INACTIVE) {
messageKey = "connection.LOW_BANDWIDTH"; messageKey = 'connection.LOW_BANDWIDTH';
} }
// Make sure no notification about remote failure is shown as // Make sure no notification about remote failure is shown as
@ -302,21 +326,23 @@ export default class LargeVideoManager {
this.videoContainer.showRemoteConnectionProblemIndicator( this.videoContainer.showRemoteConnectionProblemIndicator(
showProblemsIndication); showProblemsIndication);
if (!messageKey) { if (messageKey) {
// Hide the message
this.showRemoteConnectionMessage(false);
} else {
// Get user's display name // Get user's display name
let displayName const displayName
= APP.conference.getParticipantDisplayName(id); = APP.conference.getParticipantDisplayName(id);
this._setRemoteConnectionMessage( this._setRemoteConnectionMessage(
messageKey, messageKey,
{ displayName: displayName }); { displayName });
// Show it now only if the VideoContainer is on top // Show it now only if the VideoContainer is on top
this.showRemoteConnectionMessage( this.showRemoteConnectionMessage(
LargeVideoManager.isVideoContainer(this.state)); LargeVideoManager.isVideoContainer(this.state));
} else {
// Hide the message
this.showRemoteConnectionMessage(false);
} }
} }
/** /**
@ -327,7 +353,7 @@ export default class LargeVideoManager {
* @param {string?} videoType new video type * @param {string?} videoType new video type
* @returns {Promise} * @returns {Promise}
*/ */
updateLargeVideo (userID, stream, videoType) { updateLargeVideo(userID, stream, videoType) {
if (this.newStreamData) { if (this.newStreamData) {
this.newStreamData.reject(); this.newStreamData.reject();
} }
@ -345,7 +371,7 @@ export default class LargeVideoManager {
/** /**
* Update container size. * Update container size.
*/ */
updateContainerSize () { updateContainerSize() {
this.width = UIUtil.getAvailableVideoWidth(); this.width = UIUtil.getAvailableVideoWidth();
this.height = window.innerHeight; this.height = window.innerHeight;
} }
@ -355,8 +381,9 @@ export default class LargeVideoManager {
* @param {string} type type of container which should be resized. * @param {string} type type of container which should be resized.
* @param {boolean} [animate=false] if resize process should be animated. * @param {boolean} [animate=false] if resize process should be animated.
*/ */
resizeContainer (type, animate = false) { resizeContainer(type, animate = false) {
let container = this.getContainer(type); const container = this.getContainer(type);
container.resize(this.width, this.height, animate); container.resize(this.width, this.height, animate);
} }
@ -364,7 +391,7 @@ export default class LargeVideoManager {
* Resize all Large containers. * Resize all Large containers.
* @param {boolean} animate if resize process should be animated. * @param {boolean} animate if resize process should be animated.
*/ */
resize (animate) { resize(animate) {
// resize all containers // resize all containers
Object.keys(this.containers) Object.keys(this.containers)
.forEach(type => this.resizeContainer(type, animate)); .forEach(type => this.resizeContainer(type, animate));
@ -384,15 +411,15 @@ export default class LargeVideoManager {
* *
* @param enable <tt>true</tt> to enable, <tt>false</tt> to disable * @param enable <tt>true</tt> to enable, <tt>false</tt> to disable
*/ */
enableLocalConnectionProblemFilter (enable) { enableLocalConnectionProblemFilter(enable) {
this.videoContainer.enableLocalConnectionProblemFilter(enable); this.videoContainer.enableLocalConnectionProblemFilter(enable);
} }
/** /**
* Updates the src of the dominant speaker avatar * Updates the src of the dominant speaker avatar
*/ */
updateAvatar (avatarUrl) { updateAvatar(avatarUrl) {
$("#dominantSpeakerAvatar").attr('src', avatarUrl); $('#dominantSpeakerAvatar').attr('src', avatarUrl);
} }
/** /**
@ -400,8 +427,8 @@ export default class LargeVideoManager {
* *
* @param lvl the new audio level to set * @param lvl the new audio level to set
*/ */
updateLargeVideoAudioLevel (lvl) { updateLargeVideoAudioLevel(lvl) {
AudioLevels.updateLargeVideoAudioLevel("dominantSpeaker", lvl); AudioLevels.updateLargeVideoAudioLevel('dominantSpeaker', lvl);
} }
/** /**
@ -418,19 +445,18 @@ export default class LargeVideoManager {
if (isConnectionMessageVisible) { if (isConnectionMessageVisible) {
this.removePresenceLabel(); this.removePresenceLabel();
return; return;
} }
const presenceLabelContainer = $('#remotePresenceMessage'); const presenceLabelContainer = $('#remotePresenceMessage');
if (presenceLabelContainer.length) { if (presenceLabelContainer.length) {
/* jshint ignore:start */
ReactDOM.render( ReactDOM.render(
<Provider store = { APP.store }> <Provider store = { APP.store }>
<PresenceLabel participantID = { id } /> <PresenceLabel participantID = { id } />
</Provider>, </Provider>,
presenceLabelContainer.get(0)); presenceLabelContainer.get(0));
/* jshint ignore:end */
} }
} }
@ -443,9 +469,7 @@ export default class LargeVideoManager {
const presenceLabelContainer = $('#remotePresenceMessage'); const presenceLabelContainer = $('#remotePresenceMessage');
if (presenceLabelContainer.length) { if (presenceLabelContainer.length) {
/* jshint ignore:start */
ReactDOM.unmountComponentAtNode(presenceLabelContainer.get(0)); ReactDOM.unmountComponentAtNode(presenceLabelContainer.get(0));
/* jshint ignore:end */
} }
} }
@ -453,7 +477,7 @@ export default class LargeVideoManager {
* Show or hide watermark. * Show or hide watermark.
* @param {boolean} show * @param {boolean} show
*/ */
showWatermark (show) { showWatermark(show) {
$('.watermark').css('visibility', show ? 'visible' : 'hidden'); $('.watermark').css('visibility', show ? 'visible' : 'hidden');
} }
@ -463,12 +487,13 @@ export default class LargeVideoManager {
* displayed or not. If missing the condition will be based on the value * displayed or not. If missing the condition will be based on the value
* obtained from {@link APP.conference.isConnectionInterrupted}. * obtained from {@link APP.conference.isConnectionInterrupted}.
*/ */
showLocalConnectionMessage (show) { showLocalConnectionMessage(show) {
if (typeof show !== 'boolean') { if (typeof show !== 'boolean') {
// eslint-disable-next-line no-param-reassign
show = APP.conference.isConnectionInterrupted(); show = APP.conference.isConnectionInterrupted();
} }
let id = 'localConnectionMessage'; const id = 'localConnectionMessage';
UIUtil.setVisible(id, show); UIUtil.setVisible(id, show);
@ -489,11 +514,12 @@ export default class LargeVideoManager {
* obtained form "APP.conference" and the message will be displayed if * obtained form "APP.conference" and the message will be displayed if
* the user's connection is either interrupted or inactive. * the user's connection is either interrupted or inactive.
*/ */
showRemoteConnectionMessage (show) { showRemoteConnectionMessage(show) {
if (typeof show !== 'boolean') { if (typeof show !== 'boolean') {
const connStatus const connStatus
= APP.conference.getParticipantConnectionStatus(this.id); = APP.conference.getParticipantConnectionStatus(this.id);
// eslint-disable-next-line no-param-reassign
show = !APP.conference.isLocalId(this.id) show = !APP.conference.isLocalId(this.id)
&& (connStatus === JitsiParticipantConnectionStatus.INTERRUPTED && (connStatus === JitsiParticipantConnectionStatus.INTERRUPTED
|| connStatus || connStatus
@ -501,7 +527,8 @@ export default class LargeVideoManager {
} }
if (show) { if (show) {
$('#remoteConnectionMessage').css({display: "block"}); $('#remoteConnectionMessage').css({ display: 'block' });
// 'videoConnectionMessage' message conflicts with 'avatarMessage', // 'videoConnectionMessage' message conflicts with 'avatarMessage',
// so it must be hidden // so it must be hidden
this.showLocalConnectionMessage(false); this.showLocalConnectionMessage(false);
@ -520,11 +547,11 @@ export default class LargeVideoManager {
* *
* @private * @private
*/ */
_setRemoteConnectionMessage (msgKey, msgOptions) { _setRemoteConnectionMessage(msgKey, msgOptions) {
if (msgKey) { if (msgKey) {
$('#remoteConnectionMessage') $('#remoteConnectionMessage')
.attr("data-i18n", msgKey) .attr('data-i18n', msgKey)
.attr("data-i18n-options", JSON.stringify(msgOptions)); .attr('data-i18n-options', JSON.stringify(msgOptions));
APP.translation.translateElement( APP.translation.translateElement(
$('#remoteConnectionMessage'), msgOptions); $('#remoteConnectionMessage'), msgOptions);
} }
@ -539,9 +566,9 @@ export default class LargeVideoManager {
* *
* @private * @private
*/ */
_setLocalConnectionMessage (msgKey) { _setLocalConnectionMessage(msgKey) {
$('#localConnectionMessage') $('#localConnectionMessage')
.attr("data-i18n", msgKey); .attr('data-i18n', msgKey);
APP.translation.translateElement($('#localConnectionMessage')); APP.translation.translateElement($('#localConnectionMessage'));
} }
@ -550,7 +577,7 @@ export default class LargeVideoManager {
* @param {string} type container type * @param {string} type container type
* @param {LargeContainer} container container to add. * @param {LargeContainer} container container to add.
*/ */
addContainer (type, container) { addContainer(type, container) {
if (this.containers[type]) { if (this.containers[type]) {
throw new Error(`container of type ${type} already exist`); throw new Error(`container of type ${type} already exist`);
} }
@ -564,8 +591,8 @@ export default class LargeVideoManager {
* @param {string} type container type. * @param {string} type container type.
* @returns {LargeContainer} * @returns {LargeContainer}
*/ */
getContainer (type) { getContainer(type) {
let container = this.containers[type]; const container = this.containers[type];
if (!container) { if (!container) {
throw new Error(`container of type ${type} doesn't exist`); throw new Error(`container of type ${type} doesn't exist`);
@ -598,7 +625,7 @@ export default class LargeVideoManager {
* Remove Large container of specified type. * Remove Large container of specified type.
* @param {string} type container type. * @param {string} type container type.
*/ */
removeContainer (type) { removeContainer(type) {
if (!this.containers[type]) { if (!this.containers[type]) {
throw new Error(`container of type ${type} doesn't exist`); throw new Error(`container of type ${type} doesn't exist`);
} }
@ -612,15 +639,17 @@ export default class LargeVideoManager {
* @param {string} type container type. * @param {string} type container type.
* @returns {Promise} * @returns {Promise}
*/ */
showContainer (type) { showContainer(type) {
if (this.state === type) { if (this.state === type) {
return Promise.resolve(); return Promise.resolve();
} }
let oldContainer = this.containers[this.state]; const oldContainer = this.containers[this.state];
// FIXME when video is being replaced with other content we need to hide // FIXME when video is being replaced with other content we need to hide
// companion icons/messages. It would be best if the container would // companion icons/messages. It would be best if the container would
// be taking care of it by itself, but that is a bigger refactoring // be taking care of it by itself, but that is a bigger refactoring
if (LargeVideoManager.isVideoContainer(this.state)) { if (LargeVideoManager.isVideoContainer(this.state)) {
this.showWatermark(false); this.showWatermark(false);
this.showLocalConnectionMessage(false); this.showLocalConnectionMessage(false);
@ -629,7 +658,7 @@ export default class LargeVideoManager {
oldContainer.hide(); oldContainer.hide();
this.state = type; this.state = type;
let container = this.getContainer(type); const container = this.getContainer(type);
return container.show().then(() => { return container.show().then(() => {
if (LargeVideoManager.isVideoContainer(type)) { if (LargeVideoManager.isVideoContainer(type)) {
@ -638,6 +667,7 @@ export default class LargeVideoManager {
// the container would be taking care of it by itself, but that // the container would be taking care of it by itself, but that
// is a bigger refactoring // is a bigger refactoring
this.showWatermark(true); this.showWatermark(true);
// "avatar" and "video connection" can not be displayed both // "avatar" and "video connection" can not be displayed both
// at the same time, but the latter is of higher priority and it // at the same time, but the latter is of higher priority and it
// will hide the avatar one if will be displayed. // will hide the avatar one if will be displayed.

View File

@ -9,29 +9,33 @@ import { JitsiTrackEvents } from '../../../react/features/base/lib-jitsi-meet';
import { VideoTrack } from '../../../react/features/base/media'; import { VideoTrack } from '../../../react/features/base/media';
/* eslint-enable no-unused-vars */ /* eslint-enable no-unused-vars */
const logger = require("jitsi-meet-logger").getLogger(__filename); const logger = require('jitsi-meet-logger').getLogger(__filename);
import UIEvents from "../../../service/UI/UIEvents"; import UIEvents from '../../../service/UI/UIEvents';
import SmallVideo from "./SmallVideo"; import SmallVideo from './SmallVideo';
/**
*
*/
function LocalVideo(VideoLayout, emitter) { function LocalVideo(VideoLayout, emitter) {
this.videoSpanId = "localVideoContainer"; this.videoSpanId = 'localVideoContainer';
this.container = this.createContainer(); this.container = this.createContainer();
this.$container = $(this.container); this.$container = $(this.container);
$("#filmstripLocalVideo").append(this.container); $('#filmstripLocalVideo').append(this.container);
this.localVideoId = null; this.localVideoId = null;
this.bindHoverHandler(); this.bindHoverHandler();
if(config.enableLocalVideoFlip) if (config.enableLocalVideoFlip) {
this._buildContextMenu(); this._buildContextMenu();
}
this.isLocal = true; this.isLocal = true;
this.emitter = emitter; this.emitter = emitter;
this.statsPopoverLocation = interfaceConfig.VERTICAL_FILMSTRIP this.statsPopoverLocation = interfaceConfig.VERTICAL_FILMSTRIP
? 'left top' : 'top center'; ? 'left top' : 'top center';
Object.defineProperty(this, 'id', { Object.defineProperty(this, 'id', {
get: function () { get() {
return APP.conference.getMyUserId(); return APP.conference.getMyUserId();
} }
}); });
@ -49,8 +53,9 @@ function LocalVideo(VideoLayout, emitter) {
LocalVideo.prototype = Object.create(SmallVideo.prototype); LocalVideo.prototype = Object.create(SmallVideo.prototype);
LocalVideo.prototype.constructor = LocalVideo; LocalVideo.prototype.constructor = LocalVideo;
LocalVideo.prototype.createContainer = function () { LocalVideo.prototype.createContainer = function() {
const containerSpan = document.createElement('span'); const containerSpan = document.createElement('span');
containerSpan.classList.add('videocontainer'); containerSpan.classList.add('videocontainer');
containerSpan.id = this.videoSpanId; containerSpan.id = this.videoSpanId;
@ -72,24 +77,25 @@ LocalVideo.prototype.createContainer = function () {
LocalVideo.prototype.setDisplayName = function(displayName) { LocalVideo.prototype.setDisplayName = function(displayName) {
if (!this.container) { if (!this.container) {
logger.warn( logger.warn(
"Unable to set displayName - " + this.videoSpanId + `Unable to set displayName - ${this.videoSpanId
" does not exist"); } does not exist`);
return; return;
} }
this.updateDisplayName({ this.updateDisplayName({
allowEditing: true, allowEditing: true,
displayName: displayName, displayName,
displayNameSuffix: interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME, displayNameSuffix: interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME,
elementID: 'localDisplayName', elementID: 'localDisplayName',
participantID: this.id participantID: this.id
}); });
}; };
LocalVideo.prototype.changeVideo = function (stream) { LocalVideo.prototype.changeVideo = function(stream) {
this.videoStream = stream; this.videoStream = stream;
let localVideoClick = (event) => { const localVideoClick = event => {
// TODO Checking the classes is a workround to allow events to bubble // TODO Checking the classes is a workround to allow events to bubble
// into the DisplayName component if it was clicked. React's synthetic // into the DisplayName component if it was clicked. React's synthetic
// events will fire after jQuery handlers execute, so stop propogation // events will fire after jQuery handlers execute, so stop propogation
@ -125,11 +131,10 @@ LocalVideo.prototype.changeVideo = function (stream) {
this.$container.off('click'); this.$container.off('click');
this.$container.on('click', localVideoClick); this.$container.on('click', localVideoClick);
this.localVideoId = 'localVideo_' + stream.getId(); this.localVideoId = `localVideo_${stream.getId()}`;
var localVideoContainer = document.getElementById('localVideoWrapper'); const localVideoContainer = document.getElementById('localVideoWrapper');
/* jshint ignore:start */
ReactDOM.render( ReactDOM.render(
<Provider store = { APP.store }> <Provider store = { APP.store }>
<VideoTrack <VideoTrack
@ -138,13 +143,15 @@ LocalVideo.prototype.changeVideo = function (stream) {
</Provider>, </Provider>,
localVideoContainer localVideoContainer
); );
/* jshint ignore:end */
let isVideo = stream.videoType != "desktop"; // eslint-disable-next-line eqeqeq
const isVideo = stream.videoType != 'desktop';
this._enableDisableContextMenu(isVideo); this._enableDisableContextMenu(isVideo);
this.setFlipX(isVideo? APP.settings.getLocalFlipX() : false); this.setFlipX(isVideo ? APP.settings.getLocalFlipX() : false);
const endedHandler = () => {
let endedHandler = () => {
// Only remove if there is no video and not a transition state. // Only remove if there is no video and not a transition state.
// Previous non-react logic created a new video element with each track // Previous non-react logic created a new video element with each track
// removal whereas react reuses the video component so it could be the // removal whereas react reuses the video component so it could be the
@ -160,6 +167,7 @@ LocalVideo.prototype.changeVideo = function (stream) {
} }
stream.off(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler); stream.off(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
}; };
stream.on(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler); stream.on(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
}; };
@ -172,15 +180,14 @@ LocalVideo.prototype.setVisible = function(visible) {
// We toggle the hidden class as an indication to other interested parties // We toggle the hidden class as an indication to other interested parties
// that this container has been hidden on purpose. // that this container has been hidden on purpose.
this.$container.toggleClass("hidden"); this.$container.toggleClass('hidden');
// We still show/hide it as we need to overwrite the style property if we // We still show/hide it as we need to overwrite the style property if we
// want our action to take effect. Toggling the display property through // want our action to take effect. Toggling the display property through
// the above css class didn't succeed in overwriting the style. // the above css class didn't succeed in overwriting the style.
if (visible) { if (visible) {
this.$container.show(); this.$container.show();
} } else {
else {
this.$container.hide(); this.$container.hide();
} }
}; };
@ -189,39 +196,41 @@ LocalVideo.prototype.setVisible = function(visible) {
* Sets the flipX state of the video. * Sets the flipX state of the video.
* @param val {boolean} true for flipped otherwise false; * @param val {boolean} true for flipped otherwise false;
*/ */
LocalVideo.prototype.setFlipX = function (val) { LocalVideo.prototype.setFlipX = function(val) {
this.emitter.emit(UIEvents.LOCAL_FLIPX_CHANGED, val); this.emitter.emit(UIEvents.LOCAL_FLIPX_CHANGED, val);
if(!this.localVideoId) if (!this.localVideoId) {
return; return;
if(val) { }
this.selectVideoElement().addClass("flipVideoX"); if (val) {
this.selectVideoElement().addClass('flipVideoX');
} else { } else {
this.selectVideoElement().removeClass("flipVideoX"); this.selectVideoElement().removeClass('flipVideoX');
} }
}; };
/** /**
* Builds the context menu for the local video. * Builds the context menu for the local video.
*/ */
LocalVideo.prototype._buildContextMenu = function () { LocalVideo.prototype._buildContextMenu = function() {
$.contextMenu({ $.contextMenu({
selector: '#' + this.videoSpanId, selector: `#${this.videoSpanId}`,
zIndex: 10000, zIndex: 10000,
items: { items: {
flip: { flip: {
name: "Flip", name: 'Flip',
callback: () => { callback: () => {
let val = !APP.settings.getLocalFlipX(); const val = !APP.settings.getLocalFlipX();
this.setFlipX(val); this.setFlipX(val);
APP.settings.setLocalFlipX(val); APP.settings.setLocalFlipX(val);
} }
} }
}, },
events: { events: {
show : function(options){ show(options) {
options.items.flip.name = options.items.flip.name
APP.translation.generateTranslationHTML( = APP.translation.generateTranslationHTML(
"videothumbnail.flip"); 'videothumbnail.flip');
} }
} }
}); });
@ -231,9 +240,10 @@ LocalVideo.prototype._buildContextMenu = function () {
* Enables or disables the context menu for the local video. * Enables or disables the context menu for the local video.
* @param enable {boolean} true for enable, false for disable * @param enable {boolean} true for enable, false for disable
*/ */
LocalVideo.prototype._enableDisableContextMenu = function (enable) { LocalVideo.prototype._enableDisableContextMenu = function(enable) {
if(this.$container.contextMenu) if (this.$container.contextMenu) {
this.$container.contextMenu(enable); this.$container.contextMenu(enable);
}
}; };
export default LocalVideo; export default LocalVideo;

View File

@ -18,11 +18,11 @@ import {
} from '../../../react/features/remote-video-menu'; } from '../../../react/features/remote-video-menu';
/* eslint-enable no-unused-vars */ /* eslint-enable no-unused-vars */
const logger = require("jitsi-meet-logger").getLogger(__filename); const logger = require('jitsi-meet-logger').getLogger(__filename);
import SmallVideo from "./SmallVideo"; import SmallVideo from './SmallVideo';
import UIUtils from "../util/UIUtil"; import UIUtils from '../util/UIUtil';
/** /**
* Creates new instance of the <tt>RemoteVideo</tt>. * Creates new instance of the <tt>RemoteVideo</tt>.
@ -52,6 +52,7 @@ function RemoteVideo(user, VideoLayout, emitter) {
this.isLocal = false; this.isLocal = false;
this.popupMenuIsHovered = false; this.popupMenuIsHovered = false;
this._isRemoteControlSessionActive = false; this._isRemoteControlSessionActive = false;
/** /**
* The flag is set to <tt>true</tt> after the 'onplay' event has been * The flag is set to <tt>true</tt> after the 'onplay' event has been
* triggered on the current video element. It goes back to <tt>false</tt> * triggered on the current video element. It goes back to <tt>false</tt>
@ -60,6 +61,7 @@ function RemoteVideo(user, VideoLayout, emitter) {
* @type {boolean} * @type {boolean}
*/ */
this.wasVideoPlayed = false; this.wasVideoPlayed = false;
/** /**
* The flag is set to <tt>true</tt> if remote participant's video gets muted * The flag is set to <tt>true</tt> if remote participant's video gets muted
* during his media connection disruption. This is to prevent black video * during his media connection disruption. This is to prevent black video
@ -107,9 +109,11 @@ RemoteVideo.prototype.addRemoteVideoContainer = function() {
* @private * @private
* NOTE: extends SmallVideo's method * NOTE: extends SmallVideo's method
*/ */
RemoteVideo.prototype._isHovered = function () { RemoteVideo.prototype._isHovered = function() {
let isHovered = SmallVideo.prototype._isHovered.call(this) const isHovered = SmallVideo.prototype._isHovered.call(this)
|| this.popupMenuIsHovered; || this.popupMenuIsHovered;
return isHovered; return isHovered;
}; };
@ -119,7 +123,7 @@ RemoteVideo.prototype._isHovered = function () {
* @returns {Element|*} the constructed element, containing popup menu items * @returns {Element|*} the constructed element, containing popup menu items
* @private * @private
*/ */
RemoteVideo.prototype._generatePopupContent = function () { RemoteVideo.prototype._generatePopupContent = function() {
if (interfaceConfig.filmStripOnly) { if (interfaceConfig.filmStripOnly) {
return; return;
} }
@ -139,14 +143,13 @@ RemoteVideo.prototype._generatePopupContent = function () {
&& ((!APP.remoteControl.active && !this._isRemoteControlSessionActive) && ((!APP.remoteControl.active && !this._isRemoteControlSessionActive)
|| APP.remoteControl.controller.activeParticipant === this.id)) { || APP.remoteControl.controller.activeParticipant === this.id)) {
if (controller.getRequestedParticipant() === this.id) { if (controller.getRequestedParticipant() === this.id) {
onRemoteControlToggle = () => {};
remoteControlState = REMOTE_CONTROL_MENU_STATES.REQUESTING; remoteControlState = REMOTE_CONTROL_MENU_STATES.REQUESTING;
} else if (!controller.isStarted()) { } else if (controller.isStarted()) {
onRemoteControlToggle = this._requestRemoteControlPermissions;
remoteControlState = REMOTE_CONTROL_MENU_STATES.NOT_STARTED;
} else {
onRemoteControlToggle = this._stopRemoteControl; onRemoteControlToggle = this._stopRemoteControl;
remoteControlState = REMOTE_CONTROL_MENU_STATES.STARTED; remoteControlState = REMOTE_CONTROL_MENU_STATES.STARTED;
} else {
onRemoteControlToggle = this._requestRemoteControlPermissions;
remoteControlState = REMOTE_CONTROL_MENU_STATES.NOT_STARTED;
} }
} }
@ -161,7 +164,6 @@ RemoteVideo.prototype._generatePopupContent = function () {
const { isModerator } = APP.conference; const { isModerator } = APP.conference;
const participantID = this.id; const participantID = this.id;
/* jshint ignore:start */
ReactDOM.render( ReactDOM.render(
<Provider store = { APP.store }> <Provider store = { APP.store }>
<I18nextProvider i18n = { i18next }> <I18nextProvider i18n = { i18next }>
@ -169,7 +171,7 @@ RemoteVideo.prototype._generatePopupContent = function () {
initialVolumeValue = { initialVolumeValue } initialVolumeValue = { initialVolumeValue }
isAudioMuted = { this.isAudioMuted } isAudioMuted = { this.isAudioMuted }
isModerator = { isModerator } isModerator = { isModerator }
onMenuDisplay = { this._onRemoteVideoMenuDisplay.bind(this) } onMenuDisplay = {this._onRemoteVideoMenuDisplay.bind(this)}
onRemoteControlToggle = { onRemoteControlToggle } onRemoteControlToggle = { onRemoteControlToggle }
onVolumeChange = { onVolumeChange } onVolumeChange = { onVolumeChange }
participantID = { participantID } participantID = { participantID }
@ -177,10 +179,9 @@ RemoteVideo.prototype._generatePopupContent = function () {
</I18nextProvider> </I18nextProvider>
</Provider>, </Provider>,
remoteVideoMenuContainer); remoteVideoMenuContainer);
/* jshint ignore:end */
}; };
RemoteVideo.prototype._onRemoteVideoMenuDisplay = function () { RemoteVideo.prototype._onRemoteVideoMenuDisplay = function() {
this.updateRemoteVideoMenu(); this.updateRemoteVideoMenu();
}; };
@ -201,7 +202,7 @@ RemoteVideo.prototype.setRemoteControlActiveStatus = function(isActive) {
* @param {boolean} isSupported * @param {boolean} isSupported
*/ */
RemoteVideo.prototype.setRemoteControlSupport = function(isSupported = false) { RemoteVideo.prototype.setRemoteControlSupport = function(isSupported = false) {
if(this._supportsRemoteControl === isSupported) { if (this._supportsRemoteControl === isSupported) {
return; return;
} }
this._supportsRemoteControl = isSupported; this._supportsRemoteControl = isSupported;
@ -211,24 +212,26 @@ RemoteVideo.prototype.setRemoteControlSupport = function(isSupported = false) {
/** /**
* Requests permissions for remote control session. * Requests permissions for remote control session.
*/ */
RemoteVideo.prototype._requestRemoteControlPermissions = function () { RemoteVideo.prototype._requestRemoteControlPermissions = function() {
APP.remoteControl.controller.requestPermissions( APP.remoteControl.controller.requestPermissions(
this.id, this.VideoLayout.getLargeVideoWrapper()).then(result => { this.id, this.VideoLayout.getLargeVideoWrapper()).then(result => {
if(result === null) { if (result === null) {
return; return;
} }
this.updateRemoteVideoMenu(); this.updateRemoteVideoMenu();
APP.UI.messageHandler.notify( APP.UI.messageHandler.notify(
"dialog.remoteControlTitle", 'dialog.remoteControlTitle',
(result === false) ? "dialog.remoteControlDeniedMessage" result === false ? 'dialog.remoteControlDeniedMessage'
: "dialog.remoteControlAllowedMessage", : 'dialog.remoteControlAllowedMessage',
{user: this.user.getDisplayName() { user: this.user.getDisplayName()
|| interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME} || interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME }
); );
if(result === true) {//the remote control permissions has been granted if (result === true) {
// the remote control permissions has been granted
// pin the controlled participant // pin the controlled participant
let pinnedId = this.VideoLayout.getPinnedId(); const pinnedId = this.VideoLayout.getPinnedId();
if(pinnedId !== this.id) {
if (pinnedId !== this.id) {
this.VideoLayout.handleVideoThumbClicked(this.id); this.VideoLayout.handleVideoThumbClicked(this.id);
} }
} }
@ -236,10 +239,10 @@ RemoteVideo.prototype._requestRemoteControlPermissions = function () {
logger.error(error); logger.error(error);
this.updateRemoteVideoMenu(); this.updateRemoteVideoMenu();
APP.UI.messageHandler.notify( APP.UI.messageHandler.notify(
"dialog.remoteControlTitle", 'dialog.remoteControlTitle',
"dialog.remoteControlErrorMessage", 'dialog.remoteControlErrorMessage',
{user: this.user.getDisplayName() { user: this.user.getDisplayName()
|| interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME} || interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME }
); );
}); });
this.updateRemoteVideoMenu(); this.updateRemoteVideoMenu();
@ -248,7 +251,7 @@ RemoteVideo.prototype._requestRemoteControlPermissions = function () {
/** /**
* Stops remote control session. * Stops remote control session.
*/ */
RemoteVideo.prototype._stopRemoteControl = function () { RemoteVideo.prototype._stopRemoteControl = function() {
// send message about stopping // send message about stopping
APP.remoteControl.controller.stop(); APP.remoteControl.controller.stop();
this.updateRemoteVideoMenu(); this.updateRemoteVideoMenu();
@ -259,7 +262,7 @@ RemoteVideo.prototype._stopRemoteControl = function () {
* *
* @returns {Element} audio element * @returns {Element} audio element
*/ */
RemoteVideo.prototype._getAudioElement = function () { RemoteVideo.prototype._getAudioElement = function() {
return this._audioStreamElement; return this._audioStreamElement;
}; };
@ -268,8 +271,10 @@ RemoteVideo.prototype._getAudioElement = function () {
* *
* @returns {boolean} true if the volume can be adjusted. * @returns {boolean} true if the volume can be adjusted.
*/ */
RemoteVideo.prototype._canSetAudioVolume = function () { RemoteVideo.prototype._canSetAudioVolume = function() {
const audioElement = this._getAudioElement(); const audioElement = this._getAudioElement();
return audioElement && audioElement.volume !== undefined; return audioElement && audioElement.volume !== undefined;
}; };
@ -278,7 +283,7 @@ RemoteVideo.prototype._canSetAudioVolume = function () {
* *
* @param {int} newVal - The value to set the slider to. * @param {int} newVal - The value to set the slider to.
*/ */
RemoteVideo.prototype._setAudioVolume = function (newVal) { RemoteVideo.prototype._setAudioVolume = function(newVal) {
if (this._canSetAudioVolume()) { if (this._canSetAudioVolume()) {
this._getAudioElement().volume = newVal; this._getAudioElement().volume = newVal;
} }
@ -289,7 +294,7 @@ RemoteVideo.prototype._setAudioVolume = function (newVal) {
* *
* @param isMuted the new muted state to update to * @param isMuted the new muted state to update to
*/ */
RemoteVideo.prototype.updateRemoteVideoMenu = function ( RemoteVideo.prototype.updateRemoteVideoMenu = function(
isMuted = this.isAudioMuted) { isMuted = this.isAudioMuted) {
this.isAudioMuted = isMuted; this.isAudioMuted = isMuted;
@ -302,6 +307,7 @@ RemoteVideo.prototype.updateRemoteVideoMenu = function (
*/ */
RemoteVideo.prototype.setVideoMutedView = function(isMuted) { RemoteVideo.prototype.setVideoMutedView = function(isMuted) {
SmallVideo.prototype.setVideoMutedView.call(this, isMuted); SmallVideo.prototype.setVideoMutedView.call(this, isMuted);
// Update 'mutedWhileDisconnected' flag // Update 'mutedWhileDisconnected' flag
this._figureOutMutedWhileDisconnected(); this._figureOutMutedWhileDisconnected();
}; };
@ -314,6 +320,7 @@ RemoteVideo.prototype.setVideoMutedView = function(isMuted) {
*/ */
RemoteVideo.prototype._figureOutMutedWhileDisconnected = function() { RemoteVideo.prototype._figureOutMutedWhileDisconnected = function() {
const isActive = this.isConnectionActive(); const isActive = this.isConnectionActive();
if (!isActive && this.isVideoMuted) { if (!isActive && this.isVideoMuted) {
this.mutedWhileDisconnected = true; this.mutedWhileDisconnected = true;
} else if (isActive && !this.isVideoMuted) { } else if (isActive && !this.isVideoMuted) {
@ -326,7 +333,7 @@ RemoteVideo.prototype._figureOutMutedWhileDisconnected = function() {
* given <tt>parentElement</tt>. * given <tt>parentElement</tt>.
* *
*/ */
RemoteVideo.prototype.addRemoteVideoMenu = function () { RemoteVideo.prototype.addRemoteVideoMenu = function() {
if (interfaceConfig.filmStripOnly) { if (interfaceConfig.filmStripOnly) {
return; return;
} }
@ -343,22 +350,24 @@ RemoteVideo.prototype.addRemoteVideoMenu = function () {
* @param stream the MediaStream * @param stream the MediaStream
* @param isVideo <tt>true</tt> if given <tt>stream</tt> is a video one. * @param isVideo <tt>true</tt> if given <tt>stream</tt> is a video one.
*/ */
RemoteVideo.prototype.removeRemoteStreamElement = function (stream) { RemoteVideo.prototype.removeRemoteStreamElement = function(stream) {
if (!this.container) if (!this.container) {
return false; return false;
}
var isVideo = stream.isVideoTrack(); const isVideo = stream.isVideoTrack();
const elementID = SmallVideo.getStreamElementID(stream);
const select = $(`#${elementID}`);
var elementID = SmallVideo.getStreamElementID(stream);
var select = $('#' + elementID);
select.remove(); select.remove();
if (isVideo) { if (isVideo) {
this.wasVideoPlayed = false; this.wasVideoPlayed = false;
} }
logger.info((isVideo ? "Video" : "Audio") + logger.info(`${isVideo ? 'Video' : 'Audio'
" removed " + this.id, select); } removed ${this.id}`, select);
// when removing only the video element and we are on stage // when removing only the video element and we are on stage
// update the stage // update the stage
@ -386,15 +395,16 @@ RemoteVideo.prototype.isConnectionActive = function() {
* The remote video is considered "playable" once the stream has started * The remote video is considered "playable" once the stream has started
* according to the {@link #hasVideoStarted} result. * according to the {@link #hasVideoStarted} result.
* It will be allowed to display video also in * It will be allowed to display video also in
* {@link JitsiParticipantConnectionStatus.INTERRUPTED} if the video was ever played * {@link JitsiParticipantConnectionStatus.INTERRUPTED} if the video was ever
* and was not muted while not in ACTIVE state. This basically means that there * played and was not muted while not in ACTIVE state. This basically means
* is stalled video image cached that could be displayed. It's used to show * that there is stalled video image cached that could be displayed. It's used
* "grey video image" in user's thumbnail when there are connectivity issues. * to show "grey video image" in user's thumbnail when there are connectivity
* issues.
* *
* @inheritdoc * @inheritdoc
* @override * @override
*/ */
RemoteVideo.prototype.isVideoPlayable = function () { RemoteVideo.prototype.isVideoPlayable = function() {
const connectionState const connectionState
= APP.conference.getParticipantConnectionStatus(this.id); = APP.conference.getParticipantConnectionStatus(this.id);
@ -408,7 +418,7 @@ RemoteVideo.prototype.isVideoPlayable = function () {
/** /**
* @inheritDoc * @inheritDoc
*/ */
RemoteVideo.prototype.updateView = function () { RemoteVideo.prototype.updateView = function() {
this.$container.toggleClass('audio-only', APP.conference.isAudioOnly()); this.$container.toggleClass('audio-only', APP.conference.isAudioOnly());
this.updateConnectionStatusIndicator(); this.updateConnectionStatusIndicator();
@ -421,7 +431,7 @@ RemoteVideo.prototype.updateView = function () {
/** /**
* Updates the UI to reflect user's connectivity status. * Updates the UI to reflect user's connectivity status.
*/ */
RemoteVideo.prototype.updateConnectionStatusIndicator = function () { RemoteVideo.prototype.updateConnectionStatusIndicator = function() {
const connectionStatus = this.user.getConnectionStatus(); const connectionStatus = this.user.getConnectionStatus();
logger.debug(`${this.id} thumbnail connection status: ${connectionStatus}`); logger.debug(`${this.id} thumbnail connection status: ${connectionStatus}`);
@ -433,18 +443,20 @@ RemoteVideo.prototype.updateConnectionStatusIndicator = function () {
const isInterrupted const isInterrupted
= connectionStatus === JitsiParticipantConnectionStatus.INTERRUPTED; = connectionStatus === JitsiParticipantConnectionStatus.INTERRUPTED;
// Toggle thumbnail video problem filter // Toggle thumbnail video problem filter
this.selectVideoElement().toggleClass( this.selectVideoElement().toggleClass(
"videoThumbnailProblemFilter", isInterrupted); 'videoThumbnailProblemFilter', isInterrupted);
this.$avatar().toggleClass( this.$avatar().toggleClass(
"videoThumbnailProblemFilter", isInterrupted); 'videoThumbnailProblemFilter', isInterrupted);
}; };
/** /**
* Removes RemoteVideo from the page. * Removes RemoteVideo from the page.
*/ */
RemoteVideo.prototype.remove = function () { RemoteVideo.prototype.remove = function() {
logger.log("Remove thumbnail", this.id); logger.log('Remove thumbnail', this.id);
this.removeAudioLevelIndicator(); this.removeAudioLevelIndicator();
@ -470,30 +482,34 @@ RemoteVideo.prototype.remove = function () {
// Make sure that the large video is updated if are removing its // Make sure that the large video is updated if are removing its
// corresponding small video. // corresponding small video.
this.VideoLayout.updateAfterThumbRemoved(this.id); this.VideoLayout.updateAfterThumbRemoved(this.id);
// Remove whole container // Remove whole container
if (this.container.parentNode) { if (this.container.parentNode) {
this.container.parentNode.removeChild(this.container); this.container.parentNode.removeChild(this.container);
} }
}; };
RemoteVideo.prototype.waitForPlayback = function (streamElement, stream) { RemoteVideo.prototype.waitForPlayback = function(streamElement, stream) {
const webRtcStream = stream.getOriginalStream();
const isVideo = stream.isVideoTrack();
var webRtcStream = stream.getOriginalStream();
var isVideo = stream.isVideoTrack();
if (!isVideo || webRtcStream.id === 'mixedmslabel') { if (!isVideo || webRtcStream.id === 'mixedmslabel') {
return; return;
} }
var self = this; const self = this;
// Triggers when video playback starts // Triggers when video playback starts
var onPlayingHandler = function () { const onPlayingHandler = function() {
self.wasVideoPlayed = true; self.wasVideoPlayed = true;
self.VideoLayout.remoteVideoActive(streamElement, self.id); self.VideoLayout.remoteVideoActive(streamElement, self.id);
streamElement.onplaying = null; streamElement.onplaying = null;
// Refresh to show the video // Refresh to show the video
self.updateView(); self.updateView();
}; };
streamElement.onplaying = onPlayingHandler; streamElement.onplaying = onPlayingHandler;
}; };
@ -503,23 +519,25 @@ RemoteVideo.prototype.waitForPlayback = function (streamElement, stream) {
* @returns {boolean} true if this RemoteVideo has a video stream for which * @returns {boolean} true if this RemoteVideo has a video stream for which
* the playback has been started. * the playback has been started.
*/ */
RemoteVideo.prototype.hasVideoStarted = function () { RemoteVideo.prototype.hasVideoStarted = function() {
return this.wasVideoPlayed; return this.wasVideoPlayed;
}; };
RemoteVideo.prototype.addRemoteStreamElement = function (stream) { RemoteVideo.prototype.addRemoteStreamElement = function(stream) {
if (!this.container) { if (!this.container) {
return; return;
} }
let isVideo = stream.isVideoTrack(); const isVideo = stream.isVideoTrack();
isVideo ? this.videoStream = stream : this.audioStream = stream; isVideo ? this.videoStream = stream : this.audioStream = stream;
if (isVideo) if (isVideo) {
this.setVideoType(stream.videoType); this.setVideoType(stream.videoType);
}
// Add click handler. // Add click handler.
let onClickHandler = (event) => { const onClickHandler = event => {
const $source = $(event.target || event.srcElement); const $source = $(event.target || event.srcElement);
const { classList } = event.target; const { classList } = event.target;
@ -546,12 +564,15 @@ RemoteVideo.prototype.addRemoteStreamElement = function (stream) {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
} }
return false; return false;
}; };
this.container.onclick = onClickHandler; this.container.onclick = onClickHandler;
if(!stream.getOriginalStream()) if (!stream.getOriginalStream()) {
return; return;
}
let streamElement = SmallVideo.createStreamElement(stream); let streamElement = SmallVideo.createStreamElement(stream);
@ -591,8 +612,9 @@ RemoteVideo.prototype.addRemoteStreamElement = function (stream) {
*/ */
RemoteVideo.prototype.setDisplayName = function(displayName) { RemoteVideo.prototype.setDisplayName = function(displayName) {
if (!this.container) { if (!this.container) {
logger.warn( "Unable to set displayName - " + this.videoSpanId + logger.warn(`Unable to set displayName - ${this.videoSpanId
" does not exist"); } does not exist`);
return; return;
} }
@ -610,7 +632,7 @@ RemoteVideo.prototype.setDisplayName = function(displayName) {
* @param videoElementId the id of local or remote video element. * @param videoElementId the id of local or remote video element.
*/ */
RemoteVideo.prototype.removeRemoteVideoMenu = function() { RemoteVideo.prototype.removeRemoteVideoMenu = function() {
var menuSpan = this.$container.find('.remotevideomenu'); const menuSpan = this.$container.find('.remotevideomenu');
if (menuSpan.length) { if (menuSpan.length) {
ReactDOM.unmountComponentAtNode(menuSpan.get(0)); ReactDOM.unmountComponentAtNode(menuSpan.get(0));
@ -625,18 +647,16 @@ RemoteVideo.prototype.removeRemoteVideoMenu = function() {
* *
* @return {void} * @return {void}
*/ */
RemoteVideo.prototype.addPresenceLabel = function () { RemoteVideo.prototype.addPresenceLabel = function() {
const presenceLabelContainer const presenceLabelContainer
= this.container.querySelector('.presence-label-container'); = this.container.querySelector('.presence-label-container');
if (presenceLabelContainer) { if (presenceLabelContainer) {
/* jshint ignore:start */
ReactDOM.render( ReactDOM.render(
<Provider store = { APP.store }> <Provider store = { APP.store }>
<PresenceLabel participantID = { this.id } /> <PresenceLabel participantID = { this.id } />
</Provider>, </Provider>,
presenceLabelContainer); presenceLabelContainer);
/* jshint ignore:end */
} }
}; };
@ -645,7 +665,7 @@ RemoteVideo.prototype.addPresenceLabel = function () {
* *
* @return {void} * @return {void}
*/ */
RemoteVideo.prototype.removePresenceLabel = function () { RemoteVideo.prototype.removePresenceLabel = function() {
const presenceLabelContainer const presenceLabelContainer
= this.container.querySelector('.presence-label-container'); = this.container.querySelector('.presence-label-container');
@ -654,8 +674,9 @@ RemoteVideo.prototype.removePresenceLabel = function () {
} }
}; };
RemoteVideo.createContainer = function (spanId) { RemoteVideo.createContainer = function(spanId) {
const container = document.createElement('span'); const container = document.createElement('span');
container.id = spanId; container.id = spanId;
container.className = 'videocontainer'; container.className = 'videocontainer';
@ -669,7 +690,9 @@ RemoteVideo.createContainer = function (spanId) {
<div class ='presence-label-container'></div> <div class ='presence-label-container'></div>
<span class = 'remotevideomenu'></span>`; <span class = 'remotevideomenu'></span>`;
var remotes = document.getElementById('filmstripRemoteVideosContainer'); const remotes = document.getElementById('filmstripRemoteVideosContainer');
return remotes.appendChild(container); return remotes.appendChild(container);
}; };

View File

@ -25,11 +25,11 @@ import {
} from '../../../react/features/filmstrip'; } from '../../../react/features/filmstrip';
/* eslint-enable no-unused-vars */ /* eslint-enable no-unused-vars */
const logger = require("jitsi-meet-logger").getLogger(__filename); const logger = require('jitsi-meet-logger').getLogger(__filename);
import Avatar from "../avatar/Avatar"; import Avatar from '../avatar/Avatar';
import UIUtil from "../util/UIUtil"; import UIUtil from '../util/UIUtil';
import UIEvents from "../../../service/UI/UIEvents"; import UIEvents from '../../../service/UI/UIEvents';
const RTCUIHelper = JitsiMeetJS.util.RTCUIHelper; const RTCUIHelper = JitsiMeetJS.util.RTCUIHelper;
@ -39,6 +39,7 @@ const RTCUIHelper = JitsiMeetJS.util.RTCUIHelper;
* @constant * @constant
*/ */
const DISPLAY_VIDEO = 0; const DISPLAY_VIDEO = 0;
/** /**
* Display mode constant used when the user's avatar is being displayed on * Display mode constant used when the user's avatar is being displayed on
* the small video. * the small video.
@ -46,6 +47,7 @@ const DISPLAY_VIDEO = 0;
* @constant * @constant
*/ */
const DISPLAY_AVATAR = 1; const DISPLAY_AVATAR = 1;
/** /**
* Display mode constant used when neither video nor avatar is being displayed * Display mode constant used when neither video nor avatar is being displayed
* on the small video. And we just show the display name. * on the small video. And we just show the display name.
@ -70,6 +72,9 @@ const DISPLAY_VIDEO_WITH_NAME = 3;
*/ */
const DISPLAY_AVATAR_WITH_NAME = 4; const DISPLAY_AVATAR_WITH_NAME = 4;
/**
* Constructor.
*/
function SmallVideo(VideoLayout) { function SmallVideo(VideoLayout) {
this._isModerator = false; this._isModerator = false;
this.isAudioMuted = false; this.isAudioMuted = false;
@ -80,6 +85,7 @@ function SmallVideo(VideoLayout) {
this.VideoLayout = VideoLayout; this.VideoLayout = VideoLayout;
this.videoIsHovered = false; this.videoIsHovered = false;
this.hideDisplayName = false; this.hideDisplayName = false;
// we can stop updating the thumbnail // we can stop updating the thumbnail
this.disableUpdateView = false; this.disableUpdateView = false;
@ -136,7 +142,7 @@ function SmallVideo(VideoLayout) {
* *
* @returns the identifier of this small video * @returns the identifier of this small video
*/ */
SmallVideo.prototype.getId = function () { SmallVideo.prototype.getId = function() {
return this.id; return this.id;
}; };
@ -145,7 +151,7 @@ SmallVideo.prototype.getId = function () {
* @return <tt>true</tt> if this small video isn't currently visible and * @return <tt>true</tt> if this small video isn't currently visible and
* <tt>false</tt> - otherwise. * <tt>false</tt> - otherwise.
*/ */
SmallVideo.prototype.isVisible = function () { SmallVideo.prototype.isVisible = function() {
return this.$container.is(':visible'); return this.$container.is(':visible');
}; };
@ -153,9 +159,10 @@ SmallVideo.prototype.isVisible = function () {
* Enables / disables the device availability icons for this small video. * Enables / disables the device availability icons for this small video.
* @param {enable} set to {true} to enable and {false} to disable * @param {enable} set to {true} to enable and {false} to disable
*/ */
SmallVideo.prototype.enableDeviceAvailabilityIcons = function (enable) { SmallVideo.prototype.enableDeviceAvailabilityIcons = function(enable) {
if (typeof enable === "undefined") if (typeof enable === 'undefined') {
return; return;
}
this.deviceAvailabilityIconsEnabled = enable; this.deviceAvailabilityIconsEnabled = enable;
}; };
@ -164,32 +171,34 @@ SmallVideo.prototype.enableDeviceAvailabilityIcons = function (enable) {
* Sets the device "non" availability icons. * Sets the device "non" availability icons.
* @param devices the devices, which will be checked for availability * @param devices the devices, which will be checked for availability
*/ */
SmallVideo.prototype.setDeviceAvailabilityIcons = function (devices) { SmallVideo.prototype.setDeviceAvailabilityIcons = function(devices) {
if (!this.deviceAvailabilityIconsEnabled) if (!this.deviceAvailabilityIconsEnabled) {
return; return;
}
if(!this.container) if (!this.container) {
return; return;
}
var noMic = this.$container.find('.noMic'); const noMic = this.$container.find('.noMic');
var noVideo = this.$container.find('.noVideo'); const noVideo = this.$container.find('.noVideo');
noMic.remove(); noMic.remove();
noVideo.remove(); noVideo.remove();
if (!devices.audio) { if (!devices.audio) {
this.container.appendChild( this.container.appendChild(
document.createElement("div")).setAttribute("class", "noMic"); document.createElement('div')).setAttribute('class', 'noMic');
} }
if (!devices.video) { if (!devices.video) {
this.container.appendChild( this.container.appendChild(
document.createElement("div")).setAttribute("class", "noVideo"); document.createElement('div')).setAttribute('class', 'noVideo');
} }
if (!devices.audio && !devices.video) { if (!devices.audio && !devices.video) {
noMic.css("background-position", "75%"); noMic.css('background-position', '75%');
noVideo.css("background-position", "25%"); noVideo.css('background-position', '25%');
noVideo.css("background-color", "transparent"); noVideo.css('background-color', 'transparent');
} }
}; };
@ -200,7 +209,7 @@ SmallVideo.prototype.setDeviceAvailabilityIcons = function (devices) {
* lib-jitsi-meet. * lib-jitsi-meet.
* @param videoType 'camera' or 'desktop', or 'sharedvideo'. * @param videoType 'camera' or 'desktop', or 'sharedvideo'.
*/ */
SmallVideo.prototype.setVideoType = function (videoType) { SmallVideo.prototype.setVideoType = function(videoType) {
this.videoType = videoType; this.videoType = videoType;
}; };
@ -211,21 +220,22 @@ SmallVideo.prototype.setVideoType = function (videoType) {
* lib-jitsi-meet. * lib-jitsi-meet.
* @returns {String} 'camera', 'screen', 'sharedvideo', or undefined. * @returns {String} 'camera', 'screen', 'sharedvideo', or undefined.
*/ */
SmallVideo.prototype.getVideoType = function () { SmallVideo.prototype.getVideoType = function() {
return this.videoType; return this.videoType;
}; };
/** /**
* Creates an audio or video element for a particular MediaStream. * Creates an audio or video element for a particular MediaStream.
*/ */
SmallVideo.createStreamElement = function (stream) { SmallVideo.createStreamElement = function(stream) {
let isVideo = stream.isVideoTrack(); const isVideo = stream.isVideoTrack();
let element = isVideo const element = isVideo
? document.createElement('video') ? document.createElement('video')
: document.createElement('audio'); : document.createElement('audio');
if (isVideo) { if (isVideo) {
element.setAttribute("muted", "true"); element.setAttribute('muted', 'true');
} }
RTCUIHelper.setAutoPlay(element, true); RTCUIHelper.setAutoPlay(element, true);
@ -238,8 +248,8 @@ SmallVideo.createStreamElement = function (stream) {
/** /**
* Returns the element id for a particular MediaStream. * Returns the element id for a particular MediaStream.
*/ */
SmallVideo.getStreamElementID = function (stream) { SmallVideo.getStreamElementID = function(stream) {
let isVideo = stream.isVideoTrack(); const isVideo = stream.isVideoTrack();
return (isVideo ? 'remoteVideo_' : 'remoteAudio_') + stream.getId(); return (isVideo ? 'remoteVideo_' : 'remoteAudio_') + stream.getId();
}; };
@ -247,7 +257,7 @@ SmallVideo.getStreamElementID = function (stream) {
/** /**
* Configures hoverIn/hoverOut handlers. Depends on connection indicator. * Configures hoverIn/hoverOut handlers. Depends on connection indicator.
*/ */
SmallVideo.prototype.bindHoverHandler = function () { SmallVideo.prototype.bindHoverHandler = function() {
// Add hover handler // Add hover handler
this.$container.hover( this.$container.hover(
() => { () => {
@ -268,7 +278,7 @@ SmallVideo.prototype.bindHoverHandler = function () {
* @returns {void} * @returns {void}
*/ */
SmallVideo.prototype.removeConnectionIndicator = function () { SmallVideo.prototype.removeConnectionIndicator = function() {
this._showConnectionIndicator = false; this._showConnectionIndicator = false;
this.updateIndicators(); this.updateIndicators();
@ -279,7 +289,7 @@ SmallVideo.prototype.removeConnectionIndicator = function () {
* @returns {void} * @returns {void}
*/ */
SmallVideo.prototype.updateConnectionStatus = function (connectionStatus) { SmallVideo.prototype.updateConnectionStatus = function(connectionStatus) {
this._connectionStatus = connectionStatus; this._connectionStatus = connectionStatus;
this.updateIndicators(); this.updateIndicators();
}; };
@ -290,7 +300,7 @@ SmallVideo.prototype.updateConnectionStatus = function (connectionStatus) {
* @param {boolean} isMuted indicates if the muted element should be shown * @param {boolean} isMuted indicates if the muted element should be shown
* or hidden * or hidden
*/ */
SmallVideo.prototype.showAudioIndicator = function (isMuted) { SmallVideo.prototype.showAudioIndicator = function(isMuted) {
this.isAudioMuted = isMuted; this.isAudioMuted = isMuted;
this.updateStatusBar(); this.updateStatusBar();
}; };
@ -315,12 +325,11 @@ SmallVideo.prototype.setVideoMutedView = function(isMuted) {
* *
* @returns {void} * @returns {void}
*/ */
SmallVideo.prototype.updateStatusBar = function () { SmallVideo.prototype.updateStatusBar = function() {
const statusBarContainer const statusBarContainer
= this.container.querySelector('.videocontainer__toolbar'); = this.container.querySelector('.videocontainer__toolbar');
const tooltipPosition = interfaceConfig.VERTICAL_FILMSTRIP ? 'left' : 'top'; const tooltipPosition = interfaceConfig.VERTICAL_FILMSTRIP ? 'left' : 'top';
/* jshint ignore:start */
ReactDOM.render( ReactDOM.render(
<I18nextProvider i18n = { i18next }> <I18nextProvider i18n = { i18next }>
<div> <div>
@ -339,13 +348,12 @@ SmallVideo.prototype.updateStatusBar = function () {
</div> </div>
</I18nextProvider>, </I18nextProvider>,
statusBarContainer); statusBarContainer);
/* jshint ignore:end */
}; };
/** /**
* Adds the element indicating the moderator(owner) of the conference. * Adds the element indicating the moderator(owner) of the conference.
*/ */
SmallVideo.prototype.addModeratorIndicator = function () { SmallVideo.prototype.addModeratorIndicator = function() {
this._isModerator = true; this._isModerator = true;
this.updateStatusBar(); this.updateStatusBar();
}; };
@ -355,7 +363,7 @@ SmallVideo.prototype.addModeratorIndicator = function () {
* *
* @returns {void} * @returns {void}
*/ */
SmallVideo.prototype.addAudioLevelIndicator = function () { SmallVideo.prototype.addAudioLevelIndicator = function() {
let audioLevelContainer = this._getAudioLevelContainer(); let audioLevelContainer = this._getAudioLevelContainer();
if (audioLevelContainer) { if (audioLevelContainer) {
@ -374,7 +382,7 @@ SmallVideo.prototype.addAudioLevelIndicator = function () {
* *
* @returns {void} * @returns {void}
*/ */
SmallVideo.prototype.removeAudioLevelIndicator = function () { SmallVideo.prototype.removeAudioLevelIndicator = function() {
const audioLevelContainer = this._getAudioLevelContainer(); const audioLevelContainer = this._getAudioLevelContainer();
if (audioLevelContainer) { if (audioLevelContainer) {
@ -388,16 +396,14 @@ SmallVideo.prototype.removeAudioLevelIndicator = function () {
* @param lvl the new audio level to set * @param lvl the new audio level to set
* @returns {void} * @returns {void}
*/ */
SmallVideo.prototype.updateAudioLevelIndicator = function (lvl = 0) { SmallVideo.prototype.updateAudioLevelIndicator = function(lvl = 0) {
const audioLevelContainer = this._getAudioLevelContainer(); const audioLevelContainer = this._getAudioLevelContainer();
if (audioLevelContainer) { if (audioLevelContainer) {
/* jshint ignore:start */
ReactDOM.render( ReactDOM.render(
<AudioLevelIndicator <AudioLevelIndicator
audioLevel = { lvl }/>, audioLevel = { lvl }/>,
audioLevelContainer); audioLevelContainer);
/* jshint ignore:end */
} }
}; };
@ -407,14 +413,14 @@ SmallVideo.prototype.updateAudioLevelIndicator = function (lvl = 0) {
* *
* @returns {HTMLElement} The DOM element that holds the AudioLevelIndicator. * @returns {HTMLElement} The DOM element that holds the AudioLevelIndicator.
*/ */
SmallVideo.prototype._getAudioLevelContainer = function () { SmallVideo.prototype._getAudioLevelContainer = function() {
return this.container.querySelector('.audioindicator-container'); return this.container.querySelector('.audioindicator-container');
}; };
/** /**
* Removes the element indicating the moderator(owner) of the conference. * Removes the element indicating the moderator(owner) of the conference.
*/ */
SmallVideo.prototype.removeModeratorIndicator = function () { SmallVideo.prototype.removeModeratorIndicator = function() {
this._isModerator = false; this._isModerator = false;
this.updateStatusBar(); this.updateStatusBar();
}; };
@ -429,7 +435,7 @@ SmallVideo.prototype.removeModeratorIndicator = function () {
* this function to access the video element via the 0th element of the returned * this function to access the video element via the 0th element of the returned
* array (after checking its length of course!). * array (after checking its length of course!).
*/ */
SmallVideo.prototype.selectVideoElement = function () { SmallVideo.prototype.selectVideoElement = function() {
return $(RTCUIHelper.findVideoElement(this.container)); return $(RTCUIHelper.findVideoElement(this.container));
}; };
@ -439,7 +445,7 @@ SmallVideo.prototype.selectVideoElement = function () {
* @return {jQuery|HTMLElement} a jQuery selector pointing to the HTML image * @return {jQuery|HTMLElement} a jQuery selector pointing to the HTML image
* element which displays the user's avatar. * element which displays the user's avatar.
*/ */
SmallVideo.prototype.$avatar = function () { SmallVideo.prototype.$avatar = function() {
return this.$container.find('.avatar-container'); return this.$container.find('.avatar-container');
}; };
@ -449,7 +455,7 @@ SmallVideo.prototype.$avatar = function () {
* @return {jQuery} a jQuery selector pointing to the display name element of * @return {jQuery} a jQuery selector pointing to the display name element of
* the video thumbnail * the video thumbnail
*/ */
SmallVideo.prototype.$displayName = function () { SmallVideo.prototype.$displayName = function() {
return this.$container.find('.displayNameContainer'); return this.$container.find('.displayNameContainer');
}; };
@ -459,12 +465,11 @@ SmallVideo.prototype.$displayName = function () {
* *
* @returns {void} * @returns {void}
*/ */
SmallVideo.prototype.updateDisplayName = function (props) { SmallVideo.prototype.updateDisplayName = function(props) {
const displayNameContainer const displayNameContainer
= this.container.querySelector('.displayNameContainer'); = this.container.querySelector('.displayNameContainer');
if (displayNameContainer) { if (displayNameContainer) {
/* jshint ignore:start */
ReactDOM.render( ReactDOM.render(
<Provider store = { APP.store }> <Provider store = { APP.store }>
<I18nextProvider i18n = { i18next }> <I18nextProvider i18n = { i18next }>
@ -472,7 +477,6 @@ SmallVideo.prototype.updateDisplayName = function (props) {
</I18nextProvider> </I18nextProvider>
</Provider>, </Provider>,
displayNameContainer); displayNameContainer);
/* jshint ignore:end */
} }
}; };
@ -482,7 +486,7 @@ SmallVideo.prototype.updateDisplayName = function (props) {
* *
* @returns {void} * @returns {void}
*/ */
SmallVideo.prototype.removeDisplayName = function () { SmallVideo.prototype.removeDisplayName = function() {
const displayNameContainer const displayNameContainer
= this.container.querySelector('.displayNameContainer'); = this.container.querySelector('.displayNameContainer');
@ -498,18 +502,17 @@ SmallVideo.prototype.removeDisplayName = function () {
* @param isFocused indicates if the thumbnail should be focused/pinned or not * @param isFocused indicates if the thumbnail should be focused/pinned or not
*/ */
SmallVideo.prototype.focus = function(isFocused) { SmallVideo.prototype.focus = function(isFocused) {
var focusedCssClass = "videoContainerFocused"; const focusedCssClass = 'videoContainerFocused';
var isFocusClassEnabled = this.$container.hasClass(focusedCssClass); const isFocusClassEnabled = this.$container.hasClass(focusedCssClass);
if (!isFocused && isFocusClassEnabled) { if (!isFocused && isFocusClassEnabled) {
this.$container.removeClass(focusedCssClass); this.$container.removeClass(focusedCssClass);
} } else if (isFocused && !isFocusClassEnabled) {
else if (isFocused && !isFocusClassEnabled) {
this.$container.addClass(focusedCssClass); this.$container.addClass(focusedCssClass);
} }
}; };
SmallVideo.prototype.hasVideo = function () { SmallVideo.prototype.hasVideo = function() {
return this.selectVideoElement().length !== 0; return this.selectVideoElement().length !== 0;
}; };
@ -520,7 +523,7 @@ SmallVideo.prototype.hasVideo = function () {
* @return {boolean} <tt>true</tt> if the user is displayed on the large video * @return {boolean} <tt>true</tt> if the user is displayed on the large video
* or <tt>false</tt> otherwise. * or <tt>false</tt> otherwise.
*/ */
SmallVideo.prototype.isCurrentlyOnLargeVideo = function () { SmallVideo.prototype.isCurrentlyOnLargeVideo = function() {
return this.VideoLayout.isCurrentlyOnLarge(this.id); return this.VideoLayout.isCurrentlyOnLarge(this.id);
}; };
@ -550,13 +553,14 @@ SmallVideo.prototype.selectDisplayMode = function() {
&& this.selectVideoElement().length && this.selectVideoElement().length
&& !APP.conference.isAudioOnly()) { && !APP.conference.isAudioOnly()) {
// check hovering and change state to video with name // check hovering and change state to video with name
return this._isHovered() ? return this._isHovered()
DISPLAY_VIDEO_WITH_NAME : DISPLAY_VIDEO; ? DISPLAY_VIDEO_WITH_NAME : DISPLAY_VIDEO;
} else {
// check hovering and change state to avatar with name
return this._isHovered() ?
DISPLAY_AVATAR_WITH_NAME : DISPLAY_AVATAR;
} }
// check hovering and change state to avatar with name
return this._isHovered()
? DISPLAY_AVATAR_WITH_NAME : DISPLAY_AVATAR;
}; };
/** /**
@ -565,7 +569,7 @@ SmallVideo.prototype.selectDisplayMode = function() {
* indicator is shown(hovered). * indicator is shown(hovered).
* @private * @private
*/ */
SmallVideo.prototype._isHovered = function () { SmallVideo.prototype._isHovered = function() {
return this.videoIsHovered || this._popoverIsHovered; return this.videoIsHovered || this._popoverIsHovered;
}; };
@ -577,42 +581,49 @@ SmallVideo.prototype._isHovered = function () {
* @param show whether we should show the avatar or not * @param show whether we should show the avatar or not
* video because there is no dominant speaker and no focused speaker * video because there is no dominant speaker and no focused speaker
*/ */
SmallVideo.prototype.updateView = function () { SmallVideo.prototype.updateView = function() {
if (this.disableUpdateView) if (this.disableUpdateView) {
return; return;
}
if (!this.hasAvatar) { if (!this.hasAvatar) {
if (this.id) { if (this.id) {
// Init avatar // Init avatar
this.avatarChanged(Avatar.getAvatarUrl(this.id)); this.avatarChanged(Avatar.getAvatarUrl(this.id));
} else { } else {
logger.error("Unable to init avatar - no id", this); logger.error('Unable to init avatar - no id', this);
return; return;
} }
} }
// Determine whether video, avatar or blackness should be displayed // Determine whether video, avatar or blackness should be displayed
let displayMode = this.selectDisplayMode(); const displayMode = this.selectDisplayMode();
// Show/hide video. // Show/hide video.
UIUtil.setVisibleBySelector(this.selectVideoElement(), UIUtil.setVisibleBySelector(this.selectVideoElement(),
(displayMode === DISPLAY_VIDEO displayMode === DISPLAY_VIDEO
|| displayMode === DISPLAY_VIDEO_WITH_NAME)); || displayMode === DISPLAY_VIDEO_WITH_NAME);
// Show/hide the avatar. // Show/hide the avatar.
UIUtil.setVisibleBySelector(this.$avatar(), UIUtil.setVisibleBySelector(this.$avatar(),
(displayMode === DISPLAY_AVATAR displayMode === DISPLAY_AVATAR
|| displayMode === DISPLAY_AVATAR_WITH_NAME)); || displayMode === DISPLAY_AVATAR_WITH_NAME);
// Show/hide the display name. // Show/hide the display name.
UIUtil.setVisibleBySelector(this.$displayName(), UIUtil.setVisibleBySelector(this.$displayName(),
!this.hideDisplayName !this.hideDisplayName
&& (displayMode === DISPLAY_BLACKNESS_WITH_NAME && (displayMode === DISPLAY_BLACKNESS_WITH_NAME
|| displayMode === DISPLAY_VIDEO_WITH_NAME || displayMode === DISPLAY_VIDEO_WITH_NAME
|| displayMode === DISPLAY_AVATAR_WITH_NAME)); || displayMode === DISPLAY_AVATAR_WITH_NAME));
// show hide overlay when there is a video or avatar under // show hide overlay when there is a video or avatar under
// the display name // the display name
UIUtil.setVisibleBySelector(this.$container.find( UIUtil.setVisibleBySelector(this.$container.find(
'.videocontainer__hoverOverlay'), '.videocontainer__hoverOverlay'),
(displayMode === DISPLAY_AVATAR_WITH_NAME displayMode === DISPLAY_AVATAR_WITH_NAME
|| displayMode === DISPLAY_VIDEO_WITH_NAME)); || displayMode === DISPLAY_VIDEO_WITH_NAME);
}; };
/** /**
@ -622,19 +633,18 @@ SmallVideo.prototype.updateView = function () {
* @param {string} avatarUrl - The uri to the avatar image. * @param {string} avatarUrl - The uri to the avatar image.
* @returns {void} * @returns {void}
*/ */
SmallVideo.prototype.avatarChanged = function (avatarUrl) { SmallVideo.prototype.avatarChanged = function(avatarUrl) {
const thumbnail = this.$avatar().get(0); const thumbnail = this.$avatar().get(0);
this.hasAvatar = true; this.hasAvatar = true;
if (thumbnail) { if (thumbnail) {
/* jshint ignore:start */
ReactDOM.render( ReactDOM.render(
<AvatarDisplay <AvatarDisplay
className = 'userAvatar' className = 'userAvatar'
uri = { avatarUrl } />, uri = { avatarUrl } />,
thumbnail thumbnail
); );
/* jshint ignore:end */
} }
}; };
@ -644,7 +654,7 @@ SmallVideo.prototype.avatarChanged = function (avatarUrl) {
* *
* @returns {void} * @returns {void}
*/ */
SmallVideo.prototype.removeAvatar = function () { SmallVideo.prototype.removeAvatar = function() {
const thumbnail = this.$avatar().get(0); const thumbnail = this.$avatar().get(0);
if (thumbnail) { if (thumbnail) {
@ -656,15 +666,17 @@ SmallVideo.prototype.removeAvatar = function () {
* Shows or hides the dominant speaker indicator. * Shows or hides the dominant speaker indicator.
* @param show whether to show or hide. * @param show whether to show or hide.
*/ */
SmallVideo.prototype.showDominantSpeakerIndicator = function (show) { SmallVideo.prototype.showDominantSpeakerIndicator = function(show) {
// Don't create and show dominant speaker indicator if // Don't create and show dominant speaker indicator if
// DISABLE_DOMINANT_SPEAKER_INDICATOR is true // DISABLE_DOMINANT_SPEAKER_INDICATOR is true
if (interfaceConfig.DISABLE_DOMINANT_SPEAKER_INDICATOR) if (interfaceConfig.DISABLE_DOMINANT_SPEAKER_INDICATOR) {
return; return;
}
if (!this.container) { if (!this.container) {
logger.warn( "Unable to set dominant speaker indicator - " logger.warn(`Unable to set dominant speaker indicator - ${
+ this.videoSpanId + " does not exist"); this.videoSpanId} does not exist`);
return; return;
} }
@ -677,10 +689,11 @@ SmallVideo.prototype.showDominantSpeakerIndicator = function (show) {
* Shows or hides the raised hand indicator. * Shows or hides the raised hand indicator.
* @param show whether to show or hide. * @param show whether to show or hide.
*/ */
SmallVideo.prototype.showRaisedHandIndicator = function (show) { SmallVideo.prototype.showRaisedHandIndicator = function(show) {
if (!this.container) { if (!this.container) {
logger.warn( "Unable to raised hand indication - " logger.warn(`Unable to raised hand indication - ${
+ this.videoSpanId + " does not exist"); this.videoSpanId} does not exist`);
return; return;
} }
@ -695,26 +708,31 @@ SmallVideo.prototype.showRaisedHandIndicator = function (show) {
* is added, and will fire a RESOLUTION_CHANGED event. * is added, and will fire a RESOLUTION_CHANGED event.
*/ */
SmallVideo.prototype.waitForResolutionChange = function() { SmallVideo.prototype.waitForResolutionChange = function() {
let beforeChange = window.performance.now(); const beforeChange = window.performance.now();
let videos = this.selectVideoElement(); const videos = this.selectVideoElement();
if (!videos || !videos.length || videos.length <= 0)
if (!videos || !videos.length || videos.length <= 0) {
return; return;
let video = videos[0]; }
let oldWidth = video.videoWidth; const video = videos[0];
let oldHeight = video.videoHeight; const oldWidth = video.videoWidth;
const oldHeight = video.videoHeight;
video.onresize = () => { video.onresize = () => {
// eslint-disable-next-line eqeqeq
if (video.videoWidth != oldWidth || video.videoHeight != oldHeight) { if (video.videoWidth != oldWidth || video.videoHeight != oldHeight) {
// Only run once. // Only run once.
video.onresize = null; video.onresize = null;
let delay = window.performance.now() - beforeChange; const delay = window.performance.now() - beforeChange;
let emitter = this.VideoLayout.getEventEmitter(); const emitter = this.VideoLayout.getEventEmitter();
if (emitter) { if (emitter) {
emitter.emit( emitter.emit(
UIEvents.RESOLUTION_CHANGED, UIEvents.RESOLUTION_CHANGED,
this.getId(), this.getId(),
oldWidth + "x" + oldHeight, `${oldWidth}x${oldHeight}`,
video.videoWidth + "x" + video.videoHeight, `${video.videoWidth}x${video.videoHeight}`,
delay); delay);
} }
} }
@ -735,11 +753,12 @@ SmallVideo.prototype.waitForResolutionChange = function() {
*/ */
SmallVideo.prototype.initBrowserSpecificProperties = function() { SmallVideo.prototype.initBrowserSpecificProperties = function() {
var userAgent = window.navigator.userAgent; const userAgent = window.navigator.userAgent;
if (userAgent.indexOf("QtWebEngine") > -1
&& (userAgent.indexOf("Windows") > -1 if (userAgent.indexOf('QtWebEngine') > -1
|| userAgent.indexOf("Linux") > -1)) { && (userAgent.indexOf('Windows') > -1
this.$container.css("overflow", "hidden"); || userAgent.indexOf('Linux') > -1)) {
this.$container.css('overflow', 'hidden');
} }
}; };
@ -751,7 +770,7 @@ SmallVideo.prototype.initBrowserSpecificProperties = function() {
* @private * @private
* @returns {void} * @returns {void}
*/ */
SmallVideo.prototype.updateIndicators = function () { SmallVideo.prototype.updateIndicators = function() {
const indicatorToolbar const indicatorToolbar
= this.container.querySelector('.videocontainer__toptoolbar'); = this.container.querySelector('.videocontainer__toptoolbar');
@ -760,7 +779,6 @@ SmallVideo.prototype.updateIndicators = function () {
|| !interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_ENABLED; || !interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_ENABLED;
const tooltipPosition = interfaceConfig.VERTICAL_FILMSTRIP ? 'left' : 'top'; const tooltipPosition = interfaceConfig.VERTICAL_FILMSTRIP ? 'left' : 'top';
/* jshint ignore:start */
ReactDOM.render( ReactDOM.render(
<I18nextProvider i18n = { i18next }> <I18nextProvider i18n = { i18next }>
<div> <div>
@ -788,7 +806,6 @@ SmallVideo.prototype.updateIndicators = function () {
</I18nextProvider>, </I18nextProvider>,
indicatorToolbar indicatorToolbar
); );
/* jshint ignore:end */
}; };
/** /**
@ -798,7 +815,7 @@ SmallVideo.prototype.updateIndicators = function () {
* @private * @private
* @returns {void} * @returns {void}
*/ */
SmallVideo.prototype._unmountIndicators = function () { SmallVideo.prototype._unmountIndicators = function() {
const indicatorToolbar const indicatorToolbar
= this.container.querySelector('.videocontainer__toptoolbar'); = this.container.querySelector('.videocontainer__toptoolbar');
@ -815,7 +832,7 @@ SmallVideo.prototype._unmountIndicators = function () {
* currently over the connection indicator popover. * currently over the connection indicator popover.
* @returns {void} * @returns {void}
*/ */
SmallVideo.prototype._onPopoverHover = function (popoverIsHovered) { SmallVideo.prototype._onPopoverHover = function(popoverIsHovered) {
this._popoverIsHovered = popoverIsHovered; this._popoverIsHovered = popoverIsHovered;
this.updateView(); this.updateView();
}; };

View File

@ -23,16 +23,17 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
* @param videoSpaceHeight the height of the available space * @param videoSpaceHeight the height of the available space
* @return an array with 2 elements, the video width and the video height * @return an array with 2 elements, the video width and the video height
*/ */
function computeDesktopVideoSize( function computeDesktopVideoSize( // eslint-disable-line max-params
videoWidth, videoWidth,
videoHeight, videoHeight,
videoSpaceWidth, videoSpaceWidth,
videoSpaceHeight) { videoSpaceHeight) {
let aspectRatio = videoWidth / videoHeight; const aspectRatio = videoWidth / videoHeight;
let availableWidth = Math.max(videoWidth, videoSpaceWidth); let availableWidth = Math.max(videoWidth, videoSpaceWidth);
let availableHeight = Math.max(videoHeight, videoSpaceHeight); let availableHeight = Math.max(videoHeight, videoSpaceHeight);
// eslint-disable-next-line no-param-reassign
videoSpaceHeight -= Filmstrip.getFilmstripHeight(); videoSpaceHeight -= Filmstrip.getFilmstripHeight();
if (availableWidth / aspectRatio >= videoSpaceHeight) { if (availableWidth / aspectRatio >= videoSpaceHeight) {
@ -60,13 +61,14 @@ function computeDesktopVideoSize(
* @param videoSpaceHeight the height of the video space * @param videoSpaceHeight the height of the video space
* @return an array with 2 elements, the video width and the video height * @return an array with 2 elements, the video width and the video height
*/ */
function computeCameraVideoSize( function computeCameraVideoSize( // eslint-disable-line max-params
videoWidth, videoWidth,
videoHeight, videoHeight,
videoSpaceWidth, videoSpaceWidth,
videoSpaceHeight, videoSpaceHeight,
videoLayoutFit) { videoLayoutFit) {
const aspectRatio = videoWidth / videoHeight; const aspectRatio = videoWidth / videoHeight;
switch (videoLayoutFit) { switch (videoLayoutFit) {
case 'height': case 'height':
return [ videoSpaceHeight * aspectRatio, videoSpaceHeight ]; return [ videoSpaceHeight * aspectRatio, videoSpaceHeight ];
@ -78,10 +80,10 @@ function computeCameraVideoSize(
|| Infinity; || Infinity;
if (videoSpaceRatio === aspectRatio) { if (videoSpaceRatio === aspectRatio) {
return [videoSpaceWidth, videoSpaceHeight]; return [ videoSpaceWidth, videoSpaceHeight ];
} }
let [ width, height] = computeCameraVideoSize( let [ width, height ] = computeCameraVideoSize(
videoWidth, videoWidth,
videoHeight, videoHeight,
videoSpaceWidth, videoSpaceWidth,
@ -97,7 +99,8 @@ function computeCameraVideoSize(
height = maxHeight; height = maxHeight;
width = height * aspectRatio; width = height * aspectRatio;
} }
return [width, height];
return [ width, height ];
} }
default: default:
return [ videoWidth, videoHeight ]; return [ videoWidth, videoHeight ];
@ -111,7 +114,7 @@ function computeCameraVideoSize(
* @return an array with 2 elements, the horizontal indent and the vertical * @return an array with 2 elements, the horizontal indent and the vertical
* indent * indent
*/ */
function getCameraVideoPosition( function getCameraVideoPosition( // eslint-disable-line max-params
videoWidth, videoWidth,
videoHeight, videoHeight,
videoSpaceWidth, videoSpaceWidth,
@ -120,13 +123,15 @@ function getCameraVideoPosition(
// full screen mode and this is why we use the screen height in this case. // full screen mode and this is why we use the screen height in this case.
// Need to think it further at some point and implement it properly. // Need to think it further at some point and implement it properly.
if (UIUtil.isFullScreen()) { if (UIUtil.isFullScreen()) {
// eslint-disable-next-line no-param-reassign
videoSpaceHeight = window.innerHeight; videoSpaceHeight = window.innerHeight;
} }
let horizontalIndent = (videoSpaceWidth - videoWidth) / 2; const horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
let verticalIndent = (videoSpaceHeight - videoHeight) / 2; const verticalIndent = (videoSpaceHeight - videoHeight) / 2;
return { horizontalIndent, verticalIndent }; return { horizontalIndent,
verticalIndent };
} }
/** /**
@ -137,11 +142,12 @@ function getCameraVideoPosition(
* indent * indent
*/ */
function getDesktopVideoPosition(videoWidth, videoHeight, videoSpaceWidth) { function getDesktopVideoPosition(videoWidth, videoHeight, videoSpaceWidth) {
let horizontalIndent = (videoSpaceWidth - videoWidth) / 2; const horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
let verticalIndent = 0;// Top aligned const verticalIndent = 0;// Top aligned
return { horizontalIndent, verticalIndent }; return { horizontalIndent,
verticalIndent };
} }
/** /**
@ -149,15 +155,24 @@ function getDesktopVideoPosition(videoWidth, videoHeight, videoSpaceWidth) {
*/ */
export class VideoContainer extends LargeContainer { export class VideoContainer extends LargeContainer {
// FIXME: With Temasys we have to re-select everytime // FIXME: With Temasys we have to re-select everytime
get $video () { /**
*
*/
get $video() {
return $('#largeVideo'); return $('#largeVideo');
} }
/**
*
*/
get $videoBackground() { get $videoBackground() {
return $('#largeVideoBackground'); return $('#largeVideoBackground');
} }
get id () { /**
*
*/
get id() {
return this.userId; return this.userId;
} }
@ -168,7 +183,7 @@ export class VideoContainer extends LargeContainer {
* @param emitter {EventEmitter} the event emitter that will be used by * @param emitter {EventEmitter} the event emitter that will be used by
* this instance. * this instance.
*/ */
constructor (resizeContainer, emitter) { constructor(resizeContainer, emitter) {
super(); super();
this.stream = null; this.stream = null;
this.userId = null; this.userId = null;
@ -213,15 +228,17 @@ export class VideoContainer extends LargeContainer {
this.avatarHeight = $('#dominantSpeakerAvatar').height(); this.avatarHeight = $('#dominantSpeakerAvatar').height();
var onPlayingCallback = function (event) { const onPlayingCallback = function(event) {
if (typeof resizeContainer === 'function') { if (typeof resizeContainer === 'function') {
resizeContainer(event); resizeContainer(event);
} }
this.wasVideoRendered = true; this.wasVideoRendered = true;
}.bind(this); }.bind(this);
// This does not work with Temasys plugin - has to be a property to be // This does not work with Temasys plugin - has to be a property to be
// copied between new <object> elements // copied between new <object> elements
//this.$video.on('play', onPlay); // this.$video.on('play', onPlay);
this.$video[0].onplaying = onPlayingCallback; this.$video[0].onplaying = onPlayingCallback;
/** /**
@ -254,7 +271,7 @@ export class VideoContainer extends LargeContainer {
* @param {boolean} enable <tt>true</tt> if the filter is to be enabled or * @param {boolean} enable <tt>true</tt> if the filter is to be enabled or
* <tt>false</tt> otherwise. * <tt>false</tt> otherwise.
*/ */
enableLocalConnectionProblemFilter (enable) { enableLocalConnectionProblemFilter(enable) {
this.$video.toggleClass('videoProblemFilter', enable); this.$video.toggleClass('videoProblemFilter', enable);
this.$videoBackground.toggleClass('videoProblemFilter', enable); this.$videoBackground.toggleClass('videoProblemFilter', enable);
} }
@ -271,8 +288,10 @@ export class VideoContainer extends LargeContainer {
* Get size of video element. * Get size of video element.
* @returns {{width, height}} * @returns {{width, height}}
*/ */
getStreamSize () { getStreamSize() {
let video = this.$video[0]; const video = this.$video[0];
return { return {
width: video.videoWidth, width: video.videoWidth,
height: video.videoHeight height: video.videoHeight
@ -286,7 +305,7 @@ export class VideoContainer extends LargeContainer {
* @returns {{availableWidth, availableHeight}} * @returns {{availableWidth, availableHeight}}
*/ */
getVideoSize(containerWidth, containerHeight) { getVideoSize(containerWidth, containerHeight) {
let { width, height } = this.getStreamSize(); const { width, height } = this.getStreamSize();
if (this.stream && this.isScreenSharing()) { if (this.stream && this.isScreenSharing()) {
return computeDesktopVideoSize(width, return computeDesktopVideoSize(width,
@ -302,6 +321,7 @@ export class VideoContainer extends LargeContainer {
interfaceConfig.VIDEO_LAYOUT_FIT); interfaceConfig.VIDEO_LAYOUT_FIT);
} }
/* eslint-disable max-params */
/** /**
* Calculate optimal video position (offset for top left corner) * Calculate optimal video position (offset for top left corner)
* for specified video size and container size. * for specified video size and container size.
@ -311,18 +331,20 @@ export class VideoContainer extends LargeContainer {
* @param {number} containerHeight container height * @param {number} containerHeight container height
* @returns {{horizontalIndent, verticalIndent}} * @returns {{horizontalIndent, verticalIndent}}
*/ */
getVideoPosition (width, height, containerWidth, containerHeight) { getVideoPosition(width, height, containerWidth, containerHeight) {
/* eslint-enable max-params */
if (this.stream && this.isScreenSharing()) { if (this.stream && this.isScreenSharing()) {
return getDesktopVideoPosition( width, return getDesktopVideoPosition(width,
height,
containerWidth,
containerHeight);
} else {
return getCameraVideoPosition( width,
height, height,
containerWidth, containerWidth,
containerHeight); containerHeight);
} }
return getCameraVideoPosition(width,
height,
containerWidth,
containerHeight);
} }
/** /**
@ -346,18 +368,23 @@ export class VideoContainer extends LargeContainer {
*/ */
_positionParticipantStatus($element) { _positionParticipantStatus($element) {
if (this.avatarDisplayed) { if (this.avatarDisplayed) {
let $avatarImage = $('#dominantSpeakerAvatar'); const $avatarImage = $('#dominantSpeakerAvatar');
$element.css( $element.css(
'top', 'top',
$avatarImage.offset().top + $avatarImage.height() + 10); $avatarImage.offset().top + $avatarImage.height() + 10);
} else { } else {
let height = $element.height(); const height = $element.height();
let parentHeight = $element.parent().height(); const parentHeight = $element.parent().height();
$element.css('top', (parentHeight/2) - (height/2));
$element.css('top', (parentHeight / 2) - (height / 2));
} }
} }
resize (containerWidth, containerHeight, animate = false) { /**
*
*/
resize(containerWidth, containerHeight, animate = false) {
// XXX Prevent TypeError: undefined is not an object when the Web // XXX Prevent TypeError: undefined is not an object when the Web
// browser does not support WebRTC (yet). // browser does not support WebRTC (yet).
if (this.$video.length === 0) { if (this.$video.length === 0) {
@ -366,32 +393,35 @@ export class VideoContainer extends LargeContainer {
this._hideVideoBackground(); this._hideVideoBackground();
let [ width, height ] const [ width, height ]
= this.getVideoSize(containerWidth, containerHeight); = this.getVideoSize(containerWidth, containerHeight);
if ((containerWidth > width) || (containerHeight > height)) { if ((containerWidth > width) || (containerHeight > height)) {
this._showVideoBackground(); this._showVideoBackground();
const css const css
= containerWidth > width = containerWidth > width
? { width: '100%', height: 'auto' } ? { width: '100%',
: { width: 'auto', height: '100%' }; height: 'auto' }
: { width: 'auto',
height: '100%' };
this.$videoBackground.css(css); this.$videoBackground.css(css);
} }
let { horizontalIndent, verticalIndent } const { horizontalIndent, verticalIndent }
= this.getVideoPosition(width, height, = this.getVideoPosition(width, height,
containerWidth, containerHeight); containerWidth, containerHeight);
// update avatar position // update avatar position
let top = containerHeight / 2 - this.avatarHeight / 4 * 3; const top = (containerHeight / 2) - (this.avatarHeight / 4 * 3);
this.$avatar.css('top', top); this.$avatar.css('top', top);
this.positionRemoteStatusMessages(); this.positionRemoteStatusMessages();
this.$wrapper.animate({ this.$wrapper.animate({
width: width, width,
height: height, height,
top: verticalIndent, top: verticalIndent,
bottom: verticalIndent, bottom: verticalIndent,
@ -422,22 +452,24 @@ export class VideoContainer extends LargeContainer {
* @param {JitsiTrack?} stream new stream * @param {JitsiTrack?} stream new stream
* @param {string} videoType video type * @param {string} videoType video type
*/ */
setStream (userID, stream, videoType) { setStream(userID, stream, videoType) {
this.userId = userID; this.userId = userID;
if (this.stream === stream) { if (this.stream === stream) {
// Handles the use case for the remote participants when the // Handles the use case for the remote participants when the
// videoType is received with delay after turning on/off the // videoType is received with delay after turning on/off the
// desktop sharing. // desktop sharing.
if(this.videoType !== videoType) { if (this.videoType !== videoType) {
this.videoType = videoType; this.videoType = videoType;
this.resizeContainer(); this.resizeContainer();
} }
return; return;
} else {
// The stream has changed, so the image will be lost on detach
this.wasVideoRendered = false;
} }
// The stream has changed, so the image will be lost on detach
this.wasVideoRendered = false;
// detach old stream // detach old stream
if (this.stream) { if (this.stream) {
this.stream.detach(this.$video[0]); this.stream.detach(this.$video[0]);
@ -475,8 +507,9 @@ export class VideoContainer extends LargeContainer {
*/ */
setLocalFlipX(val) { setLocalFlipX(val) {
this.localFlipX = val; this.localFlipX = val;
if(!this.$video || !this.stream || !this.stream.isLocal()) if (!this.$video || !this.stream || !this.stream.isLocal()) {
return; return;
}
this.$video.css({ this.$video.css({
transform: this.localFlipX ? 'scaleX(-1)' : 'none' transform: this.localFlipX ? 'scaleX(-1)' : 'none'
}); });
@ -491,7 +524,7 @@ export class VideoContainer extends LargeContainer {
* Check if current video stream is screen sharing. * Check if current video stream is screen sharing.
* @returns {boolean} * @returns {boolean}
*/ */
isScreenSharing () { isScreenSharing() {
return this.videoType === 'desktop'; return this.videoType === 'desktop';
} }
@ -499,7 +532,7 @@ export class VideoContainer extends LargeContainer {
* Show or hide user avatar. * Show or hide user avatar.
* @param {boolean} show * @param {boolean} show
*/ */
showAvatar (show) { showAvatar(show) {
// TO FIX: Video background need to be black, so that we don't have a // TO FIX: Video background need to be black, so that we don't have a
// flickering effect when scrolling between videos and have the screen // flickering effect when scrolling between videos and have the screen
// move to grey before going back to video. Avatars though can have the // move to grey before going back to video. Avatars though can have the
@ -521,26 +554,28 @@ export class VideoContainer extends LargeContainer {
* @param {boolean} show <tt>true</tt> to show or <tt>false</tt> to hide * @param {boolean} show <tt>true</tt> to show or <tt>false</tt> to hide
* the indication. * the indication.
*/ */
showRemoteConnectionProblemIndicator (show) { showRemoteConnectionProblemIndicator(show) {
this.$video.toggleClass('remoteVideoProblemFilter', show); this.$video.toggleClass('remoteVideoProblemFilter', show);
this.$videoBackground.toggleClass('remoteVideoProblemFilter', show); this.$videoBackground.toggleClass('remoteVideoProblemFilter', show);
this.$avatar.toggleClass('remoteVideoProblemFilter', show); this.$avatar.toggleClass('remoteVideoProblemFilter', show);
} }
// We are doing fadeOut/fadeIn animations on parent div which wraps
// largeVideo, because when Temasys plugin is in use it replaces
// <video> elements with plugin <object> tag. In Safari jQuery is
// unable to store values on this plugin object which breaks all
// animation effects performed on it directly.
show () { /**
* We are doing fadeOut/fadeIn animations on parent div which wraps
* largeVideo, because when Temasys plugin is in use it replaces
* <video> elements with plugin <object> tag. In Safari jQuery is
* unable to store values on this plugin object which breaks all
* animation effects performed on it directly.
*/
show() {
// its already visible // its already visible
if (this.isVisible) { if (this.isVisible) {
return Promise.resolve(); return Promise.resolve();
} }
return new Promise((resolve) => { return new Promise(resolve => {
this.$wrapperParent.css('visibility', 'visible').fadeTo( this.$wrapperParent.css('visibility', 'visible').fadeTo(
FADE_DURATION_MS, FADE_DURATION_MS,
1, 1,
@ -552,16 +587,20 @@ export class VideoContainer extends LargeContainer {
}); });
} }
hide () { /**
*
*/
hide() {
// as the container is hidden/replaced by another container // as the container is hidden/replaced by another container
// hide its avatar // hide its avatar
this.showAvatar(false); this.showAvatar(false);
// its already hidden // its already hidden
if (!this.isVisible) { if (!this.isVisible) {
return Promise.resolve(); return Promise.resolve();
} }
return new Promise((resolve) => { return new Promise(resolve => {
this.$wrapperParent.fadeTo(FADE_DURATION_MS, 0, () => { this.$wrapperParent.fadeTo(FADE_DURATION_MS, 0, () => {
this.$wrapperParent.css('visibility', 'hidden'); this.$wrapperParent.css('visibility', 'hidden');
this.isVisible = false; this.isVisible = false;
@ -573,7 +612,7 @@ export class VideoContainer extends LargeContainer {
/** /**
* @return {boolean} switch on dominant speaker event if on stage. * @return {boolean} switch on dominant speaker event if on stage.
*/ */
stayOnStage () { stayOnStage() {
return false; return false;
} }
@ -586,9 +625,9 @@ export class VideoContainer extends LargeContainer {
* on the large video. * on the large video.
* @returns {void} * @returns {void}
*/ */
setLargeVideoBackground (isAvatar) { setLargeVideoBackground(isAvatar) {
$('#largeVideoContainer').css('background', $('#largeVideoContainer').css('background',
(this.videoType === VIDEO_CONTAINER_TYPE && !isAvatar) this.videoType === VIDEO_CONTAINER_TYPE && !isAvatar
? '#000' : interfaceConfig.DEFAULT_BACKGROUND); ? '#000' : interfaceConfig.DEFAULT_BACKGROUND);
} }
@ -631,6 +670,7 @@ export class VideoContainer extends LargeContainer {
// do not care. Some browsers (at this time, only Edge is known) don't // do not care. Some browsers (at this time, only Edge is known) don't
// return a promise from .play(), so check before trying to catch. // return a promise from .play(), so check before trying to catch.
const res = this.$videoBackground[0].play(); const res = this.$videoBackground[0].play();
if (typeof res !== 'undefined') { if (typeof res !== 'undefined') {
res.catch(reason => logger.error(reason)); res.catch(reason => logger.error(reason));
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
const logger = require("jitsi-meet-logger").getLogger(__filename); const logger = require('jitsi-meet-logger').getLogger(__filename);
/** /**
* The modules stores information about the URL used to start the conference and * The modules stores information about the URL used to start the conference and
@ -24,9 +24,9 @@ export default class ConferenceUrl {
* from the sample URL. * from the sample URL.
*/ */
constructor(location) { constructor(location) {
logger.info("Stored original conference URL: " + location.href); logger.info(`Stored original conference URL: ${location.href}`);
logger.info( logger.info(
"Conference URL for invites: " + location.protocol + "//" `Conference URL for invites: ${location.protocol}//${
+ location.host + location.pathname); location.host}${location.pathname}`);
} }
} }

View File

@ -1,8 +1,8 @@
/* global APP, JitsiMeetJS */ /* global APP, JitsiMeetJS */
let currentAudioInputDevices, let currentAudioInputDevices,
currentVideoInputDevices, currentAudioOutputDevices,
currentAudioOutputDevices; currentVideoInputDevices;
/** /**
* Determines if currently selected audio output device should be changed after * Determines if currently selected audio output device should be changed after
@ -16,14 +16,14 @@ function getNewAudioOutputDevice(newDevices) {
return; return;
} }
let selectedAudioOutputDeviceId = APP.settings.getAudioOutputDeviceId(); const selectedAudioOutputDeviceId = APP.settings.getAudioOutputDeviceId();
let availableAudioOutputDevices = newDevices.filter( const availableAudioOutputDevices = newDevices.filter(
d => d.kind === 'audiooutput'); d => d.kind === 'audiooutput');
// Switch to 'default' audio output device if we don't have the selected one // Switch to 'default' audio output device if we don't have the selected one
// available anymore. // available anymore.
if (selectedAudioOutputDeviceId !== 'default' && if (selectedAudioOutputDeviceId !== 'default'
!availableAudioOutputDevices.find(d => && !availableAudioOutputDevices.find(d =>
d.deviceId === selectedAudioOutputDeviceId)) { d.deviceId === selectedAudioOutputDeviceId)) {
return 'default'; return 'default';
} }
@ -38,10 +38,10 @@ function getNewAudioOutputDevice(newDevices) {
* if audio input device should not be changed. * if audio input device should not be changed.
*/ */
function getNewAudioInputDevice(newDevices, localAudio) { function getNewAudioInputDevice(newDevices, localAudio) {
let availableAudioInputDevices = newDevices.filter( const availableAudioInputDevices = newDevices.filter(
d => d.kind === 'audioinput'); d => d.kind === 'audioinput');
let selectedAudioInputDeviceId = APP.settings.getMicDeviceId(); const selectedAudioInputDeviceId = APP.settings.getMicDeviceId();
let selectedAudioInputDevice = availableAudioInputDevices.find( const selectedAudioInputDevice = availableAudioInputDevices.find(
d => d.deviceId === selectedAudioInputDeviceId); d => d.deviceId === selectedAudioInputDeviceId);
// Here we handle case when no device was initially plugged, but // Here we handle case when no device was initially plugged, but
@ -51,18 +51,17 @@ function getNewAudioInputDevice(newDevices, localAudio) {
// If we have new audio device and permission to use it was granted // If we have new audio device and permission to use it was granted
// (label is not an empty string), then we will try to use the first // (label is not an empty string), then we will try to use the first
// available device. // available device.
if (availableAudioInputDevices.length && if (availableAudioInputDevices.length
availableAudioInputDevices[0].label !== '') { && availableAudioInputDevices[0].label !== '') {
return availableAudioInputDevices[0].deviceId; return availableAudioInputDevices[0].deviceId;
} }
} else { } else if (selectedAudioInputDevice
&& selectedAudioInputDeviceId !== localAudio.getDeviceId()) {
// And here we handle case when we already have some device working, // And here we handle case when we already have some device working,
// but we plug-in a "preferred" (previously selected in settings, stored // but we plug-in a "preferred" (previously selected in settings, stored
// in local storage) device. // in local storage) device.
if (selectedAudioInputDevice && return selectedAudioInputDeviceId;
selectedAudioInputDeviceId !== localAudio.getDeviceId()) {
return selectedAudioInputDeviceId;
}
} }
} }
@ -75,10 +74,10 @@ function getNewAudioInputDevice(newDevices, localAudio) {
* if video input device should not be changed. * if video input device should not be changed.
*/ */
function getNewVideoInputDevice(newDevices, localVideo) { function getNewVideoInputDevice(newDevices, localVideo) {
let availableVideoInputDevices = newDevices.filter( const availableVideoInputDevices = newDevices.filter(
d => d.kind === 'videoinput'); d => d.kind === 'videoinput');
let selectedVideoInputDeviceId = APP.settings.getCameraDeviceId(); const selectedVideoInputDeviceId = APP.settings.getCameraDeviceId();
let selectedVideoInputDevice = availableVideoInputDevices.find( const selectedVideoInputDevice = availableVideoInputDevices.find(
d => d.deviceId === selectedVideoInputDeviceId); d => d.deviceId === selectedVideoInputDeviceId);
// Here we handle case when no video input device was initially plugged, // Here we handle case when no video input device was initially plugged,
@ -88,18 +87,16 @@ function getNewVideoInputDevice(newDevices, localVideo) {
// If we have new video device and permission to use it was granted // If we have new video device and permission to use it was granted
// (label is not an empty string), then we will try to use the first // (label is not an empty string), then we will try to use the first
// available device. // available device.
if (availableVideoInputDevices.length && if (availableVideoInputDevices.length
availableVideoInputDevices[0].label !== '') { && availableVideoInputDevices[0].label !== '') {
return availableVideoInputDevices[0].deviceId; return availableVideoInputDevices[0].deviceId;
} }
} else { } else if (selectedVideoInputDevice
&& selectedVideoInputDeviceId !== localVideo.getDeviceId()) {
// And here we handle case when we already have some device working, // And here we handle case when we already have some device working,
// but we plug-in a "preferred" (previously selected in settings, stored // but we plug-in a "preferred" (previously selected in settings, stored
// in local storage) device. // in local storage) device.
if (selectedVideoInputDevice && return selectedVideoInputDeviceId;
selectedVideoInputDeviceId !== localVideo.getDeviceId()) {
return selectedVideoInputDeviceId;
}
} }
} }
@ -113,19 +110,21 @@ export default {
getDevicesFromListByKind(devices, kind) { getDevicesFromListByKind(devices, kind) {
return devices.filter(d => d.kind === kind); return devices.filter(d => d.kind === kind);
}, },
/** /**
* Stores lists of current 'audioinput', 'videoinput' and 'audiooutput' * Stores lists of current 'audioinput', 'videoinput' and 'audiooutput'
* devices. * devices.
* @param {MediaDeviceInfo[]} devices * @param {MediaDeviceInfo[]} devices
*/ */
setCurrentMediaDevices(devices) { setCurrentMediaDevices(devices) {
currentAudioInputDevices = currentAudioInputDevices
this.getDevicesFromListByKind(devices, 'audioinput'); = this.getDevicesFromListByKind(devices, 'audioinput');
currentVideoInputDevices = currentVideoInputDevices
this.getDevicesFromListByKind(devices, 'videoinput'); = this.getDevicesFromListByKind(devices, 'videoinput');
currentAudioOutputDevices = currentAudioOutputDevices
this.getDevicesFromListByKind(devices, 'audiooutput'); = this.getDevicesFromListByKind(devices, 'audiooutput');
}, },
/** /**
* Returns lists of current 'audioinput', 'videoinput' and 'audiooutput' * Returns lists of current 'audioinput', 'videoinput' and 'audiooutput'
* devices. * devices.
@ -142,6 +141,7 @@ export default {
audiooutput: currentAudioOutputDevices audiooutput: currentAudioOutputDevices
}; };
}, },
/** /**
* Determines if currently selected media devices should be changed after * Determines if currently selected media devices should be changed after
* list of available devices has been changed. * list of available devices has been changed.
@ -155,18 +155,19 @@ export default {
* audiooutput: (string|undefined) * audiooutput: (string|undefined)
* }} * }}
*/ */
getNewMediaDevicesAfterDeviceListChanged( getNewMediaDevicesAfterDeviceListChanged( // eslint-disable-line max-params
newDevices, newDevices,
isSharingScreen, isSharingScreen,
localVideo, localVideo,
localAudio) { localAudio) {
return { return {
audioinput: getNewAudioInputDevice(newDevices, localAudio), audioinput: getNewAudioInputDevice(newDevices, localAudio),
videoinput: !isSharingScreen && videoinput: !isSharingScreen
getNewVideoInputDevice(newDevices, localVideo), && getNewVideoInputDevice(newDevices, localVideo),
audiooutput: getNewAudioOutputDevice(newDevices) audiooutput: getNewAudioOutputDevice(newDevices)
}; };
}, },
/** /**
* Tries to create new local tracks for new devices obtained after device * Tries to create new local tracks for new devices obtained after device
* list changed. Shows error dialog in case of failures. * list changed. Shows error dialog in case of failures.
@ -181,21 +182,22 @@ export default {
micDeviceId) { micDeviceId) {
let audioTrackError; let audioTrackError;
let videoTrackError; let videoTrackError;
let audioRequested = !!micDeviceId; const audioRequested = Boolean(micDeviceId);
let videoRequested = !!cameraDeviceId; const videoRequested = Boolean(cameraDeviceId);
if (audioRequested && videoRequested) { if (audioRequested && videoRequested) {
// First we try to create both audio and video tracks together. // First we try to create both audio and video tracks together.
return ( return (
createLocalTracks({ createLocalTracks({
devices: ['audio', 'video'], devices: [ 'audio', 'video' ],
cameraDeviceId: cameraDeviceId, cameraDeviceId,
micDeviceId: micDeviceId micDeviceId
}) })
// If we fail to do this, try to create them separately. // If we fail to do this, try to create them separately.
.catch(() => Promise.all([ .catch(() => Promise.all([
createAudioTrack(false).then(([stream]) => stream), createAudioTrack(false).then(([ stream ]) => stream),
createVideoTrack(false).then(([stream]) => stream) createVideoTrack(false).then(([ stream ]) => stream)
])) ]))
.then(tracks => { .then(tracks => {
if (audioTrackError) { if (audioTrackError) {
@ -212,34 +214,42 @@ export default {
return createVideoTrack(); return createVideoTrack();
} else if (audioRequested && !videoRequested) { } else if (audioRequested && !videoRequested) {
return createAudioTrack(); return createAudioTrack();
} else {
return Promise.resolve([]);
} }
return Promise.resolve([]);
/**
*
*/
function createAudioTrack(showError) { function createAudioTrack(showError) {
return ( return (
createLocalTracks({ createLocalTracks({
devices: ['audio'], devices: [ 'audio' ],
cameraDeviceId: null, cameraDeviceId: null,
micDeviceId: micDeviceId micDeviceId
}) })
.catch(err => { .catch(err => {
audioTrackError = err; audioTrackError = err;
showError && APP.UI.showMicErrorNotification(err); showError && APP.UI.showMicErrorNotification(err);
return []; return [];
})); }));
} }
/**
*
*/
function createVideoTrack(showError) { function createVideoTrack(showError) {
return ( return (
createLocalTracks({ createLocalTracks({
devices: ['video'], devices: [ 'video' ],
cameraDeviceId: cameraDeviceId, cameraDeviceId,
micDeviceId: null micDeviceId: null
}) })
.catch(err => { .catch(err => {
videoTrackError = err; videoTrackError = err;
showError && APP.UI.showCameraErrorNotification(err); showError && APP.UI.showCameraErrorNotification(err);
return []; return [];
})); }));
} }

View File

@ -11,47 +11,6 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
*/ */
let keyboardShortcutDialog = null; let keyboardShortcutDialog = null;
/**
* Initialise global shortcuts.
* Global shortcuts are shortcuts for features that don't have a button or
* link associated with the action. In other words they represent actions
* triggered _only_ with a shortcut.
*/
function initGlobalShortcuts() {
KeyboardShortcut.registerShortcut("ESCAPE", null, function() {
showKeyboardShortcutsPanel(false);
});
KeyboardShortcut.registerShortcut("?", null, function() {
sendEvent("shortcut.shortcut.help");
showKeyboardShortcutsPanel(true);
}, "keyboardShortcuts.toggleShortcuts");
// register SPACE shortcut in two steps to insure visibility of help message
KeyboardShortcut.registerShortcut(" ", null, function() {
sendEvent("shortcut.talk.clicked");
logger.log('Talk shortcut pressed');
APP.conference.muteAudio(true);
});
KeyboardShortcut._addShortcutToHelp("SPACE","keyboardShortcuts.pushToTalk");
if(!interfaceConfig.filmStripOnly) {
KeyboardShortcut.registerShortcut("T", null, () => {
sendEvent("shortcut.speakerStats.clicked");
APP.store.dispatch(toggleDialog(SpeakerStats, {
conference: APP.conference
}));
}, "keyboardShortcuts.showSpeakerStats");
}
/**
* FIXME: Currently focus keys are directly implemented below in onkeyup.
* They should be moved to the SmallVideo instead.
*/
KeyboardShortcut._addShortcutToHelp("0", "keyboardShortcuts.focusLocal");
KeyboardShortcut._addShortcutToHelp("1-9", "keyboardShortcuts.focusRemote");
}
/** /**
* Shows or hides the keyboard shortcuts dialog. * Shows or hides the keyboard shortcuts dialog.
* @param {boolean} show whether to show or hide the dialog * @param {boolean} show whether to show or hide the dialog
@ -60,8 +19,8 @@ function showKeyboardShortcutsPanel(show) {
if (show if (show
&& !APP.UI.messageHandler.isDialogOpened() && !APP.UI.messageHandler.isDialogOpened()
&& keyboardShortcutDialog === null) { && keyboardShortcutDialog === null) {
let msg = $('#keyboard-shortcuts').html(); const msg = $('#keyboard-shortcuts').html();
let buttons = { Close: true }; const buttons = { Close: true };
keyboardShortcutDialog = APP.UI.messageHandler.openDialog( keyboardShortcutDialog = APP.UI.messageHandler.openDialog(
'keyboardShortcuts.keyboardShortcuts', msg, true, buttons); 'keyboardShortcuts.keyboardShortcuts', msg, true, buttons);
@ -75,7 +34,7 @@ function showKeyboardShortcutsPanel(show) {
* Map of shortcuts. When a shortcut is registered it enters the mapping. * Map of shortcuts. When a shortcut is registered it enters the mapping.
* @type {{}} * @type {{}}
*/ */
let _shortcuts = {}; const _shortcuts = {};
/** /**
* True if the keyboard shortcuts are enabled and false if not. * True if the keyboard shortcuts are enabled and false if not.
@ -87,43 +46,42 @@ let enabled = true;
* Maps keycode to character, id of popover for given function and function. * Maps keycode to character, id of popover for given function and function.
*/ */
const KeyboardShortcut = { const KeyboardShortcut = {
init: function () { init() {
initGlobalShortcuts(); this._initGlobalShortcuts();
var self = this; window.onkeyup = e => {
window.onkeyup = function(e) { if (!enabled) {
if(!enabled) {
return; return;
} }
var key = self._getKeyboardKey(e).toUpperCase(); const key = this._getKeyboardKey(e).toUpperCase();
var num = parseInt(key, 10); const num = parseInt(key, 10);
if(!($(":focus").is("input[type=text]") ||
$(":focus").is("input[type=password]") || if (!($(':focus').is('input[type=text]')
$(":focus").is("textarea"))) { || $(':focus').is('input[type=password]')
|| $(':focus').is('textarea'))) {
if (_shortcuts.hasOwnProperty(key)) { if (_shortcuts.hasOwnProperty(key)) {
_shortcuts[key].function(e); _shortcuts[key].function(e);
} } else if (!isNaN(num) && num >= 0 && num <= 9) {
else if (!isNaN(num) && num >= 0 && num <= 9) {
APP.UI.clickOnVideo(num); APP.UI.clickOnVideo(num);
} }
//esc while the smileys are visible hides them
} else if (key === "ESCAPE" && // esc while the smileys are visible hides them
$('#smileysContainer').is(':visible')) { } else if (key === 'ESCAPE'
&& $('#smileysContainer').is(':visible')) {
APP.UI.toggleSmileys(); APP.UI.toggleSmileys();
} }
}; };
window.onkeydown = function(e) { window.onkeydown = e => {
if(!enabled) { if (!enabled) {
return; return;
} }
if(!($(":focus").is("input[type=text]") || if (!($(':focus').is('input[type=text]')
$(":focus").is("input[type=password]") || || $(':focus').is('input[type=password]')
$(":focus").is("textarea"))) { || $(':focus').is('textarea'))) {
var key = self._getKeyboardKey(e).toUpperCase(); if (this._getKeyboardKey(e).toUpperCase() === ' ') {
if(key === " ") { if (APP.conference.isLocalAudioMuted()) {
if(APP.conference.isLocalAudioMuted()) { sendEvent('shortcut.talk.released');
sendEvent("shortcut.talk.released");
logger.log('Talk shortcut released'); logger.log('Talk shortcut released');
APP.conference.muteAudio(false); APP.conference.muteAudio(false);
} }
@ -136,7 +94,7 @@ const KeyboardShortcut = {
* Enables/Disables the keyboard shortcuts. * Enables/Disables the keyboard shortcuts.
* @param {boolean} value - the new value. * @param {boolean} value - the new value.
*/ */
enable: function (value) { enable(value) {
enabled = value; enabled = value;
}, },
@ -151,19 +109,20 @@ const KeyboardShortcut = {
* @param helpDescription the description of the shortcut that would appear * @param helpDescription the description of the shortcut that would appear
* in the help menu * in the help menu
*/ */
registerShortcut( registerShortcut(// eslint-disable-line max-params
shortcutChar, shortcutChar,
shortcutAttr, shortcutAttr,
exec, exec,
helpDescription) { helpDescription) {
_shortcuts[shortcutChar] = { _shortcuts[shortcutChar] = {
character: shortcutChar, character: shortcutChar,
shortcutAttr: shortcutAttr, shortcutAttr,
function: exec function: exec
}; };
if (helpDescription) if (helpDescription) {
this._addShortcutToHelp(shortcutChar, helpDescription); this._addShortcutToHelp(shortcutChar, helpDescription);
}
}, },
/** /**
@ -172,7 +131,7 @@ const KeyboardShortcut = {
* @param shortcutChar unregisters the given shortcut, which means it will * @param shortcutChar unregisters the given shortcut, which means it will
* no longer be usable * no longer be usable
*/ */
unregisterShortcut: function(shortcutChar) { unregisterShortcut(shortcutChar) {
_shortcuts.remove(shortcutChar); _shortcuts.remove(shortcutChar);
this._removeShortcutFromHelp(shortcutChar); this._removeShortcutFromHelp(shortcutChar);
@ -186,44 +145,47 @@ const KeyboardShortcut = {
* or an empty string if the shortcutAttr is null, an empty string or not * or an empty string if the shortcutAttr is null, an empty string or not
* found in the shortcut mapping * found in the shortcut mapping
*/ */
getShortcutTooltip: function (shortcutAttr) { getShortcutTooltip(shortcutAttr) {
if (typeof shortcutAttr === "string" && shortcutAttr.length > 0) { if (typeof shortcutAttr === 'string' && shortcutAttr.length > 0) {
for (var key in _shortcuts) { for (const key in _shortcuts) {
if (_shortcuts.hasOwnProperty(key) if (_shortcuts.hasOwnProperty(key)
&& _shortcuts[key].shortcutAttr && _shortcuts[key].shortcutAttr
&& _shortcuts[key].shortcutAttr === shortcutAttr) { && _shortcuts[key].shortcutAttr === shortcutAttr) {
return " (" + _shortcuts[key].character + ")"; return ` (${_shortcuts[key].character})`;
} }
} }
} }
return ""; return '';
}, },
/** /**
* @param e a KeyboardEvent * @param e a KeyboardEvent
* @returns {string} e.key or something close if not supported * @returns {string} e.key or something close if not supported
*/ */
_getKeyboardKey: function (e) { _getKeyboardKey(e) {
if (typeof e.key === "string") { if (typeof e.key === 'string') {
return e.key; return e.key;
} }
if (e.type === "keypress" if (e.type === 'keypress'
&& ((e.which >= 32 && e.which <= 126) && ((e.which >= 32 && e.which <= 126)
|| (e.which >= 160 && e.which <= 255) )) { || (e.which >= 160 && e.which <= 255))) {
return String.fromCharCode(e.which); return String.fromCharCode(e.which);
} }
// try to fallback (0-9A-Za-z and QWERTY keyboard) // try to fallback (0-9A-Za-z and QWERTY keyboard)
switch (e.which) { switch (e.which) {
case 27: case 27:
return "Escape"; return 'Escape';
case 191: case 191:
return e.shiftKey ? "?" : "/"; return e.shiftKey ? '?' : '/';
} }
if (e.shiftKey || e.type === "keypress") { if (e.shiftKey || e.type === 'keypress') {
return String.fromCharCode(e.which); return String.fromCharCode(e.which);
} else {
return String.fromCharCode(e.which).toLowerCase();
} }
return String.fromCharCode(e.which).toLowerCase();
}, },
/** /**
@ -233,36 +195,41 @@ const KeyboardShortcut = {
* @param shortcutDescriptionKey the description of the shortcut * @param shortcutDescriptionKey the description of the shortcut
* @private * @private
*/ */
_addShortcutToHelp: function (shortcutChar, shortcutDescriptionKey) { _addShortcutToHelp(shortcutChar, shortcutDescriptionKey) {
const listElement = document.createElement('li');
const itemClass = 'shortcuts-list__item';
let listElement = document.createElement("li");
let itemClass = 'shortcuts-list__item';
listElement.className = itemClass; listElement.className = itemClass;
listElement.id = shortcutChar; listElement.id = shortcutChar;
let spanElement = document.createElement("span"); const spanElement = document.createElement('span');
spanElement.className = "item-action";
spanElement.className = 'item-action';
const kbdElement = document.createElement('kbd');
const classes = 'aui-label regular-key';
let kbdElement = document.createElement("kbd");
let classes = 'aui-label regular-key';
kbdElement.className = classes; kbdElement.className = classes;
kbdElement.innerHTML = shortcutChar; kbdElement.innerHTML = shortcutChar;
spanElement.appendChild(kbdElement); spanElement.appendChild(kbdElement);
let descriptionElement = document.createElement("span"); const descriptionElement = document.createElement('span');
let descriptionClass = "shortcuts-list__description"; const descriptionClass = 'shortcuts-list__description';
descriptionElement.className = descriptionClass; descriptionElement.className = descriptionClass;
descriptionElement.setAttribute("data-i18n", shortcutDescriptionKey); descriptionElement.setAttribute('data-i18n', shortcutDescriptionKey);
APP.translation.translateElement($(descriptionElement)); APP.translation.translateElement($(descriptionElement));
listElement.appendChild(spanElement); listElement.appendChild(spanElement);
listElement.appendChild(descriptionElement); listElement.appendChild(descriptionElement);
let parentListElement const parentListElement
= document.getElementById("keyboard-shortcuts-list"); = document.getElementById('keyboard-shortcuts-list');
if (parentListElement) if (parentListElement) {
parentListElement.appendChild(listElement); parentListElement.appendChild(listElement);
}
}, },
/** /**
@ -270,14 +237,57 @@ const KeyboardShortcut = {
* help dialog * help dialog
* @private * @private
*/ */
_removeShortcutFromHelp: function (shortcutChar) { _removeShortcutFromHelp(shortcutChar) {
var parentListElement const parentListElement
= document.getElementById("keyboard-shortcuts-list"); = document.getElementById('keyboard-shortcuts-list');
var shortcutElement = document.getElementById(shortcutChar); const shortcutElement = document.getElementById(shortcutChar);
if (shortcutElement) if (shortcutElement) {
parentListElement.removeChild(shortcutElement); parentListElement.removeChild(shortcutElement);
}
},
/**
* Initialise global shortcuts.
* Global shortcuts are shortcuts for features that don't have a button or
* link associated with the action. In other words they represent actions
* triggered _only_ with a shortcut.
*/
_initGlobalShortcuts() {
this.registerShortcut('ESCAPE', null, () => {
showKeyboardShortcutsPanel(false);
});
this.registerShortcut('?', null, () => {
sendEvent('shortcut.shortcut.help');
showKeyboardShortcutsPanel(true);
}, 'keyboardShortcuts.toggleShortcuts');
// register SPACE shortcut in two steps to insure visibility of help
// message
this.registerShortcut(' ', null, () => {
sendEvent('shortcut.talk.clicked');
logger.log('Talk shortcut pressed');
APP.conference.muteAudio(true);
});
this._addShortcutToHelp('SPACE', 'keyboardShortcuts.pushToTalk');
if (!interfaceConfig.filmStripOnly) {
this.registerShortcut('T', null, () => {
sendEvent('shortcut.speakerStats.clicked');
APP.store.dispatch(toggleDialog(SpeakerStats, {
conference: APP.conference
}));
}, 'keyboardShortcuts.showSpeakerStats');
}
/**
* FIXME: Currently focus keys are directly implemented below in
* onkeyup. They should be moved to the SmallVideo instead.
*/
this._addShortcutToHelp('0', 'keyboardShortcuts.focusLocal');
this._addShortcutToHelp('1-9', 'keyboardShortcuts.focusRemote');
} }
}; };

View File

@ -5,74 +5,76 @@
* @enum {string} * @enum {string}
*/ */
export const KEYS = { export const KEYS = {
BACKSPACE: "backspace" , BACKSPACE: 'backspace',
DELETE : "delete", DELETE: 'delete',
RETURN : "enter", RETURN: 'enter',
TAB : "tab", TAB: 'tab',
ESCAPE : "escape", ESCAPE: 'escape',
UP : "up", UP: 'up',
DOWN : "down", DOWN: 'down',
RIGHT : "right", RIGHT: 'right',
LEFT : "left", LEFT: 'left',
HOME : "home", HOME: 'home',
END : "end", END: 'end',
PAGEUP : "pageup", PAGEUP: 'pageup',
PAGEDOWN : "pagedown", PAGEDOWN: 'pagedown',
F1 : "f1", F1: 'f1',
F2 : "f2", F2: 'f2',
F3 : "f3", F3: 'f3',
F4 : "f4", F4: 'f4',
F5 : "f5", F5: 'f5',
F6 : "f6", F6: 'f6',
F7 : "f7", F7: 'f7',
F8 : "f8", F8: 'f8',
F9 : "f9", F9: 'f9',
F10 : "f10", F10: 'f10',
F11 : "f11", F11: 'f11',
F12 : "f12", F12: 'f12',
META : "command", META: 'command',
CMD_L: "command", CMD_L: 'command',
CMD_R: "command", CMD_R: 'command',
ALT : "alt", ALT: 'alt',
CONTROL : "control", CONTROL: 'control',
SHIFT : "shift", SHIFT: 'shift',
CAPS_LOCK: "caps_lock", //not supported by robotjs CAPS_LOCK: 'caps_lock', // not supported by robotjs
SPACE : "space", SPACE: 'space',
PRINTSCREEN : "printscreen", PRINTSCREEN: 'printscreen',
INSERT : "insert", INSERT: 'insert',
NUMPAD_0 : "numpad_0", NUMPAD_0: 'numpad_0',
NUMPAD_1 : "numpad_1", NUMPAD_1: 'numpad_1',
NUMPAD_2 : "numpad_2", NUMPAD_2: 'numpad_2',
NUMPAD_3 : "numpad_3", NUMPAD_3: 'numpad_3',
NUMPAD_4 : "numpad_4", NUMPAD_4: 'numpad_4',
NUMPAD_5 : "numpad_5", NUMPAD_5: 'numpad_5',
NUMPAD_6 : "numpad_6", NUMPAD_6: 'numpad_6',
NUMPAD_7 : "numpad_7", NUMPAD_7: 'numpad_7',
NUMPAD_8 : "numpad_8", NUMPAD_8: 'numpad_8',
NUMPAD_9 : "numpad_9", NUMPAD_9: 'numpad_9',
COMMA: ",", COMMA: ',',
PERIOD: ".", PERIOD: '.',
SEMICOLON: ";", SEMICOLON: ';',
QUOTE: "'", QUOTE: '\'',
BRACKET_LEFT: "[", BRACKET_LEFT: '[',
BRACKET_RIGHT: "]", BRACKET_RIGHT: ']',
BACKQUOTE: "`", BACKQUOTE: '`',
BACKSLASH: "\\", BACKSLASH: '\\',
MINUS: "-", MINUS: '-',
EQUAL: "=", EQUAL: '=',
SLASH: "/" SLASH: '/'
}; };
/* eslint-disable max-len */
/** /**
* Mapping between the key codes and keys deined in KEYS. * Mapping between the key codes and keys deined in KEYS.
* The mappings are based on * The mappings are based on
* https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode#Specifications * https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode#Specifications
*/ */
let keyCodeToKey = { /* eslint-enable max-len */
const keyCodeToKey = {
8: KEYS.BACKSPACE, 8: KEYS.BACKSPACE,
9: KEYS.TAB, 9: KEYS.TAB,
13: KEYS.RETURN, 13: KEYS.RETURN,
@ -141,15 +143,16 @@ let keyCodeToKey = {
/** /**
* Generate codes for digit keys (0-9) * Generate codes for digit keys (0-9)
*/ */
for(let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
keyCodeToKey[i + 48] = `${i}`; keyCodeToKey[i + 48] = `${i}`;
} }
/** /**
* Generate codes for letter keys (a-z) * Generate codes for letter keys (a-z)
*/ */
for(let i = 0; i < 26; i++) { for (let i = 0; i < 26; i++) {
let keyCode = i + 65; const keyCode = i + 65;
keyCodeToKey[keyCode] = String.fromCharCode(keyCode).toLowerCase(); keyCodeToKey[keyCode] = String.fromCharCode(keyCode).toLowerCase();
} }

View File

@ -3,16 +3,20 @@
/** /**
* The (name of the) command which transports the recorder info. * The (name of the) command which transports the recorder info.
*/ */
const _USER_INFO_COMMAND = "userinfo"; const _USER_INFO_COMMAND = 'userinfo';
/** /**
* The Recorder class is meant to take care of recorder related presence * The Recorder class is meant to take care of recorder related presence
* commands. * commands.
*/ */
class Recorder { class Recorder {
/**
* Creates new recorder instance.
*/
constructor() { constructor() {
if (config.iAmRecorder) if (config.iAmRecorder) {
this._sendRecorderInfo(); this._sendRecorderInfo();
}
} }
/** /**
@ -20,7 +24,7 @@ class Recorder {
* @private * @private
*/ */
_sendRecorderInfo() { _sendRecorderInfo() {
var commands = APP.conference.commands; const commands = APP.conference.commands;
// XXX The "Follow Me" command represents a snapshot of all states // XXX The "Follow Me" command represents a snapshot of all states
// which are to be followed so don't forget to removeCommand before // which are to be followed so don't forget to removeCommand before

View File

@ -1,5 +1,5 @@
/* global JitsiMeetJS */ /* global JitsiMeetJS */
const logger = require("jitsi-meet-logger").getLogger(__filename); const logger = require('jitsi-meet-logger').getLogger(__filename);
import UIUtil from '../UI/util/UIUtil'; import UIUtil from '../UI/util/UIUtil';
import jitsiLocalStorage from '../util/JitsiLocalStorage'; import jitsiLocalStorage from '../util/JitsiLocalStorage';
@ -7,34 +7,35 @@ import { randomHexString } from '../../react/features/base/util';
let avatarUrl = ''; let avatarUrl = '';
let email = UIUtil.unescapeHtml(jitsiLocalStorage.getItem("email") || ''); let email = UIUtil.unescapeHtml(jitsiLocalStorage.getItem('email') || '');
let avatarId = UIUtil.unescapeHtml(jitsiLocalStorage.getItem("avatarId") || ''); let avatarId = UIUtil.unescapeHtml(jitsiLocalStorage.getItem('avatarId') || '');
if (!avatarId) { if (!avatarId) {
// if there is no avatar id, we generate a unique one and use it forever // if there is no avatar id, we generate a unique one and use it forever
avatarId = randomHexString(32); avatarId = randomHexString(32);
jitsiLocalStorage.setItem("avatarId", avatarId); jitsiLocalStorage.setItem('avatarId', avatarId);
} }
let localFlipX = JSON.parse(jitsiLocalStorage.getItem("localFlipX") || true); let localFlipX = JSON.parse(jitsiLocalStorage.getItem('localFlipX') || true);
let displayName = UIUtil.unescapeHtml( let displayName = UIUtil.unescapeHtml(
jitsiLocalStorage.getItem("displayname") || ''); jitsiLocalStorage.getItem('displayname') || '');
let cameraDeviceId = jitsiLocalStorage.getItem("cameraDeviceId") || ''; let cameraDeviceId = jitsiLocalStorage.getItem('cameraDeviceId') || '';
let micDeviceId = jitsiLocalStorage.getItem("micDeviceId") || ''; let micDeviceId = jitsiLocalStorage.getItem('micDeviceId') || '';
let welcomePageDisabled = JSON.parse( let welcomePageDisabled = JSON.parse(
jitsiLocalStorage.getItem("welcomePageDisabled") || false); jitsiLocalStorage.getItem('welcomePageDisabled') || false);
// Currently audio output device change is supported only in Chrome and // Currently audio output device change is supported only in Chrome and
// default output always has 'default' device ID // default output always has 'default' device ID
let audioOutputDeviceId = jitsiLocalStorage.getItem("audioOutputDeviceId") const audioOutputDeviceId = jitsiLocalStorage.getItem('audioOutputDeviceId')
|| 'default'; || 'default';
if (audioOutputDeviceId !== if (audioOutputDeviceId
JitsiMeetJS.mediaDevices.getAudioOutputDevice()) { !== JitsiMeetJS.mediaDevices.getAudioOutputDevice()) {
JitsiMeetJS.mediaDevices.setAudioOutputDevice(audioOutputDeviceId) JitsiMeetJS.mediaDevices.setAudioOutputDevice(audioOutputDeviceId)
.catch((ex) => { .catch(ex => {
logger.warn('Failed to set audio output device from local ' + logger.warn('Failed to set audio output device from local '
'storage. Default audio output device will be used' + + 'storage. Default audio output device will be used'
'instead.', ex); + 'instead.', ex);
}); });
} }
@ -46,19 +47,20 @@ export default {
* @param {string} newDisplayName unescaped display name for the local user * @param {string} newDisplayName unescaped display name for the local user
* @param {boolean} disableLocalStore disables local store the display name * @param {boolean} disableLocalStore disables local store the display name
*/ */
setDisplayName (newDisplayName, disableLocalStore) { setDisplayName(newDisplayName, disableLocalStore) {
displayName = newDisplayName; displayName = newDisplayName;
if (!disableLocalStore) if (!disableLocalStore) {
jitsiLocalStorage.setItem("displayname", jitsiLocalStorage.setItem('displayname',
UIUtil.escapeHtml(displayName)); UIUtil.escapeHtml(displayName));
}
}, },
/** /**
* Returns the escaped display name currently used by the user * Returns the escaped display name currently used by the user
* @returns {string} currently valid user display name. * @returns {string} currently valid user display name.
*/ */
getDisplayName: function () { getDisplayName() {
return displayName; return displayName;
}, },
@ -67,18 +69,19 @@ export default {
* @param {string} newEmail new email for the local user * @param {string} newEmail new email for the local user
* @param {boolean} disableLocalStore disables local store the email * @param {boolean} disableLocalStore disables local store the email
*/ */
setEmail: function (newEmail, disableLocalStore) { setEmail(newEmail, disableLocalStore) {
email = newEmail; email = newEmail;
if (!disableLocalStore) if (!disableLocalStore) {
jitsiLocalStorage.setItem("email", UIUtil.escapeHtml(newEmail)); jitsiLocalStorage.setItem('email', UIUtil.escapeHtml(newEmail));
}
}, },
/** /**
* Returns email address of the local user. * Returns email address of the local user.
* @returns {string} email * @returns {string} email
*/ */
getEmail: function () { getEmail() {
return email; return email;
}, },
@ -86,7 +89,7 @@ export default {
* Returns avatar id of the local user. * Returns avatar id of the local user.
* @returns {string} avatar id * @returns {string} avatar id
*/ */
getAvatarId: function () { getAvatarId() {
return avatarId; return avatarId;
}, },
@ -94,7 +97,7 @@ export default {
* Sets new avatarUrl for local user and saves it to the local storage. * Sets new avatarUrl for local user and saves it to the local storage.
* @param {string} newAvatarUrl new avatarUrl for the local user * @param {string} newAvatarUrl new avatarUrl for the local user
*/ */
setAvatarUrl: function (newAvatarUrl) { setAvatarUrl(newAvatarUrl) {
avatarUrl = newAvatarUrl; avatarUrl = newAvatarUrl;
}, },
@ -102,7 +105,7 @@ export default {
* Returns avatarUrl address of the local user. * Returns avatarUrl address of the local user.
* @returns {string} avatarUrl * @returns {string} avatarUrl
*/ */
getAvatarUrl: function () { getAvatarUrl() {
return avatarUrl; return avatarUrl;
}, },
@ -110,16 +113,16 @@ export default {
* Sets new flipX state of local video and saves it to the local storage. * Sets new flipX state of local video and saves it to the local storage.
* @param {string} val flipX state of local video * @param {string} val flipX state of local video
*/ */
setLocalFlipX: function (val) { setLocalFlipX(val) {
localFlipX = val; localFlipX = val;
jitsiLocalStorage.setItem("localFlipX", val); jitsiLocalStorage.setItem('localFlipX', val);
}, },
/** /**
* Returns flipX state of local video. * Returns flipX state of local video.
* @returns {string} flipX * @returns {string} flipX
*/ */
getLocalFlipX: function () { getLocalFlipX() {
return localFlipX; return localFlipX;
}, },
@ -128,19 +131,21 @@ export default {
* Empty string stands for default device. * Empty string stands for default device.
* @returns {String} * @returns {String}
*/ */
getCameraDeviceId: function () { getCameraDeviceId() {
return cameraDeviceId; return cameraDeviceId;
}, },
/** /**
* Set device id of the camera which is currently in use. * Set device id of the camera which is currently in use.
* Empty string stands for default device. * Empty string stands for default device.
* @param {string} newId new camera device id * @param {string} newId new camera device id
* @param {boolean} whether we need to store the value * @param {boolean} whether we need to store the value
*/ */
setCameraDeviceId: function (newId, store) { setCameraDeviceId(newId, store) {
cameraDeviceId = newId; cameraDeviceId = newId;
if (store) if (store) {
jitsiLocalStorage.setItem("cameraDeviceId", newId); jitsiLocalStorage.setItem('cameraDeviceId', newId);
}
}, },
/** /**
@ -148,19 +153,21 @@ export default {
* Empty string stands for default device. * Empty string stands for default device.
* @returns {String} * @returns {String}
*/ */
getMicDeviceId: function () { getMicDeviceId() {
return micDeviceId; return micDeviceId;
}, },
/** /**
* Set device id of the microphone which is currently in use. * Set device id of the microphone which is currently in use.
* Empty string stands for default device. * Empty string stands for default device.
* @param {string} newId new microphone device id * @param {string} newId new microphone device id
* @param {boolean} whether we need to store the value * @param {boolean} whether we need to store the value
*/ */
setMicDeviceId: function (newId, store) { setMicDeviceId(newId, store) {
micDeviceId = newId; micDeviceId = newId;
if (store) if (store) {
jitsiLocalStorage.setItem("micDeviceId", newId); jitsiLocalStorage.setItem('micDeviceId', newId);
}
}, },
/** /**
@ -168,26 +175,27 @@ export default {
* Empty string stands for default device. * Empty string stands for default device.
* @returns {String} * @returns {String}
*/ */
getAudioOutputDeviceId: function () { getAudioOutputDeviceId() {
return JitsiMeetJS.mediaDevices.getAudioOutputDevice(); return JitsiMeetJS.mediaDevices.getAudioOutputDevice();
}, },
/** /**
* Set device id of the audio output device which is currently in use. * Set device id of the audio output device which is currently in use.
* Empty string stands for default device. * Empty string stands for default device.
* @param {string} newId='default' - new audio output device id * @param {string} newId='default' - new audio output device id
* @returns {Promise} * @returns {Promise}
*/ */
setAudioOutputDeviceId: function (newId = 'default') { setAudioOutputDeviceId(newId = 'default') {
return JitsiMeetJS.mediaDevices.setAudioOutputDevice(newId) return JitsiMeetJS.mediaDevices.setAudioOutputDevice(newId)
.then(() => .then(() =>
jitsiLocalStorage.setItem("audioOutputDeviceId", newId)); jitsiLocalStorage.setItem('audioOutputDeviceId', newId));
}, },
/** /**
* Check if welcome page is enabled or not. * Check if welcome page is enabled or not.
* @returns {boolean} * @returns {boolean}
*/ */
isWelcomePageEnabled () { isWelcomePageEnabled() {
return !welcomePageDisabled; return !welcomePageDisabled;
}, },
@ -195,8 +203,8 @@ export default {
* Enable or disable welcome page. * Enable or disable welcome page.
* @param {boolean} enabled if welcome page should be enabled or not * @param {boolean} enabled if welcome page should be enabled or not
*/ */
setWelcomePageEnabled (enabled) { setWelcomePageEnabled(enabled) {
welcomePageDisabled = !enabled; welcomePageDisabled = !enabled;
jitsiLocalStorage.setItem("welcomePageDisabled", welcomePageDisabled); jitsiLocalStorage.setItem('welcomePageDisabled', welcomePageDisabled);
} }
}; };

View File

@ -16,11 +16,20 @@ function _onI18nInitialized() {
$('[data-i18n]').localize(); $('[data-i18n]').localize();
} }
/**
*
*/
class Translation { class Translation {
/**
*
*/
addLanguageChangedListener(listener: Function) { addLanguageChangedListener(listener: Function) {
i18next.on('languageChanged', listener); i18next.on('languageChanged', listener);
} }
/**
*
*/
generateTranslationHTML(key: string, options: Object) { generateTranslationHTML(key: string, options: Object) {
const optAttr const optAttr
= options ? ` data-i18n-options='${JSON.stringify(options)}'` : ''; = options ? ` data-i18n-options='${JSON.stringify(options)}'` : '';
@ -31,23 +40,36 @@ class Translation {
return `<span data-i18n="${key}"${optAttr}>${text}</span>`; return `<span data-i18n="${key}"${optAttr}>${text}</span>`;
} }
/**
*
*/
getCurrentLanguage() { getCurrentLanguage() {
return i18next.lng(); return i18next.lng();
} }
/**
*
*/
init() { init() {
jqueryI18next.init(i18next, $, { useOptionsAttr: true }); jqueryI18next.init(i18next, $, { useOptionsAttr: true });
if (i18next.isInitialized) if (i18next.isInitialized) {
_onI18nInitialized(); _onI18nInitialized();
else } else {
i18next.on('initialized', _onI18nInitialized); i18next.on('initialized', _onI18nInitialized);
}
} }
/**
*
*/
setLanguage(language: string = DEFAULT_LANGUAGE) { setLanguage(language: string = DEFAULT_LANGUAGE) {
i18next.setLng(language, {}, _onI18nInitialized); i18next.setLng(language, {}, _onI18nInitialized);
} }
/**
*
*/
translateElement(selector: Object, options: Object) { translateElement(selector: Object, options: Object) {
// XXX i18next expects undefined if options are missing. // XXX i18next expects undefined if options are missing.
selector.localize(options ? options : undefined); selector.localize(options ? options : undefined);

View File

@ -65,6 +65,7 @@ export default class PostMessageTransportBackend {
* transport. * transport.
*/ */
constructor({ enableLegacyFormat, postisOptions } = {}) { constructor({ enableLegacyFormat, postisOptions } = {}) {
// eslint-disable-next-line new-cap
this.postis = Postis({ this.postis = Postis({
...DEFAULT_POSTIS_OPTIONS, ...DEFAULT_POSTIS_OPTIONS,
...postisOptions ...postisOptions

View File

@ -6,6 +6,7 @@ const logger = Logger.getLogger(__filename);
* Dummy implementation of Storage interface with empty methods. * Dummy implementation of Storage interface with empty methods.
*/ */
class DummyLocalStorage { class DummyLocalStorage {
/* eslint-disable no-empty-function */
/** /**
* Empty function * Empty function
*/ */
@ -20,6 +21,7 @@ class DummyLocalStorage {
* Empty function * Empty function
*/ */
removeItem() { } removeItem() { }
/* eslint-enable no-empty-function */
} }
/** /**

View File

@ -37,15 +37,17 @@ export default class JitsiMeetLogStorage {
return; return;
} }
let logJSON = '{"log' + this.counter + '":"\n'; let logJSON = `{"log${this.counter}":"\n`;
for (let i = 0, len = logEntries.length; i < len; i++) { for (let i = 0, len = logEntries.length; i < len; i++) {
let logEntry = logEntries[i]; const logEntry = logEntries[i];
if (typeof logEntry === 'object') { if (typeof logEntry === 'object') {
// Aggregated message // Aggregated message
logJSON += '(' + logEntry.count + ') ' + logEntry.text + '\n'; logJSON += `(${logEntry.count}) ${logEntry.text}\n`;
} else { } else {
// Regular message // Regular message
logJSON += logEntry + '\n'; logJSON += `${logEntry}\n`;
} }
} }
logJSON += '"}'; logJSON += '"}';
@ -60,7 +62,7 @@ export default class JitsiMeetLogStorage {
} catch (error) { } catch (error) {
// NOTE console is intentional here // NOTE console is intentional here
console.error( console.error(
"Failed to store the logs: ", logJSON, error); 'Failed to store the logs: ', logJSON, error);
} }
} }
} }

View File

@ -1,4 +1,4 @@
const logger = require("jitsi-meet-logger").getLogger(__filename); const logger = require('jitsi-meet-logger').getLogger(__filename);
/** /**
* Create deferred object. * Create deferred object.
@ -40,7 +40,7 @@ export function replace(url) {
* @param e {Error} the error * @param e {Error} the error
* @param msg {string} [optional] the message printed in addition to the error * @param msg {string} [optional] the message printed in addition to the error
*/ */
export function reportError(e, msg = "") { export function reportError(e, msg = '') {
logger.error(msg, e); logger.error(msg, e);
window.onerror && window.onerror(msg, null, null, null, e); window.onerror && window.onerror(msg, null, null, null, e);
} }

View File

@ -6,48 +6,12 @@ module.exports = {
} }
}, },
'plugins': [ 'plugins': [
// ESLint's rule no-duplicate-imports does not understand Flow's import
// type. Fortunately, eslint-plugin-import understands Flow's import
// type.
'import',
'jsdoc', 'jsdoc',
'react', 'react',
'react-native' 'react-native'
], ],
'rules': { 'rules': {
// Possible Errors group // Possible Errors group
'no-cond-assign': 2,
'no-console': 0,
'no-constant-condition': 2,
'no-control-regex': 2,
'no-debugger': 2,
'no-dupe-args': 2,
'no-dupe-keys': 2,
'no-duplicate-case': 2,
'no-empty': 2,
'no-empty-character-class': 2,
'no-ex-assign': 2,
'no-extra-boolean-cast': 2,
'no-extra-parens': [
'error',
'all',
{ 'nestedBinaryExpressions': false }
],
'no-extra-semi': 2,
'no-func-assign': 2,
'no-inner-declarations': 2,
'no-invalid-regexp': 2,
'no-irregular-whitespace': 2,
'no-negated-in-lhs': 2,
'no-obj-calls': 2,
'no-prototype-builtins': 0,
'no-regex-spaces': 2,
'no-sparse-arrays': 2,
'no-unexpected-multiline': 2,
'no-unreachable': 2,
'no-unsafe-finally': 2,
'use-isnan': 2,
// Currently, we are using both valid-jsdoc and 'jsdoc' plugin. In the // Currently, we are using both valid-jsdoc and 'jsdoc' plugin. In the
// future we might stick to one as soon as it has all the features. // future we might stick to one as soon as it has all the features.
@ -74,234 +38,11 @@ module.exports = {
'requireReturnType': true 'requireReturnType': true
} }
], ],
'valid-typeof': 2,
// Best Practices group // Best Practices group
'accessor-pairs': 0,
'array-callback-return': 2,
'block-scoped-var': 0,
'complexity': 0,
'consistent-return': 0,
'curly': 2,
'default-case': 0,
'dot-location': [ 'error', 'property' ],
'dot-notation': 2,
'eqeqeq': 2,
'guard-for-in': 2,
'no-alert': 2,
'no-caller': 2,
'no-case-declarations': 2,
'no-div-regex': 0,
'no-else-return': 2,
'no-empty-function': 2,
'no-empty-pattern': 2,
'no-eq-null': 2,
'no-eval': 2,
'no-extend-native': 2,
'no-extra-bind': 2,
'no-extra-label': 2,
'no-fallthrough': 2,
'no-floating-decimal': 2,
'no-implicit-coercion': 2,
'no-implicit-globals': 2,
'no-implied-eval': 2,
'no-invalid-this': 2,
'no-iterator': 2,
'no-labels': 2,
'no-lone-blocks': 2,
'no-loop-func': 2,
'no-magic-numbers': 0,
'no-multi-spaces': 2,
'no-multi-str': 2,
'no-native-reassign': 2,
'no-new': 2,
'no-new-func': 2,
'no-new-wrappers': 2,
'no-octal': 2,
'no-octal-escape': 2,
'no-param-reassign': 2,
'no-proto': 2,
'no-redeclare': 2,
'no-return-assign': 2,
'no-script-url': 2,
'no-self-assign': 2,
'no-self-compare': 2,
'no-sequences': 2,
'no-throw-literal': 2,
'no-unmodified-loop-condition': 2,
'no-unused-expressions': [
'error',
{
'allowShortCircuit': true,
'allowTernary': true
}
],
'no-unused-labels': 2,
'no-useless-call': 2,
'no-useless-concat': 2,
'no-useless-escape': 2,
'no-void': 2,
'no-warning-comments': 0,
'no-with': 2,
'radix': 2,
'vars-on-top': 2,
'wrap-iife': [ 'error', 'inside' ],
'yoda': 2,
// Strict Mode group
'strict': 2,
// Variables group
'init-declarations': 0,
'no-catch-shadow': 2,
'no-delete-var': 2,
'no-label-var': 2,
'no-restricted-globals': 0,
'no-shadow': 2,
'no-shadow-restricted-names': 2,
'no-undef': 2,
'no-undef-init': 2,
'no-undefined': 0,
'no-unused-vars': 2,
'no-use-before-define': [ 'error', { 'functions': false } ],
// Stylistic issues group
'array-bracket-spacing': [
'error',
'always',
{ 'objectsInArrays': true }
],
'block-spacing': [ 'error', 'always' ],
'brace-style': 2,
'camelcase': 2,
'comma-dangle': 2,
'comma-spacing': 2,
'comma-style': 2,
'computed-property-spacing': 2,
'consistent-this': [ 'error', 'self' ],
'eol-last': 2,
'func-names': 0,
'func-style': 0,
'id-blacklist': 0,
'id-length': 0,
'id-match': 0,
'jsx-quotes': [ 'error', 'prefer-single' ], 'jsx-quotes': [ 'error', 'prefer-single' ],
'key-spacing': 2,
'keyword-spacing': 2,
'linebreak-style': [ 'error', 'unix' ],
'lines-around-comment': [
'error',
{
'allowBlockStart': true,
'allowObjectStart': true,
'beforeBlockComment': true,
'beforeLineComment': true
}
],
'max-depth': 2,
'max-len': [ 'error', 80 ],
'max-lines': 0,
'max-nested-callbacks': 2,
'max-params': 2,
'max-statements': 0,
'max-statements-per-line': 2,
'multiline-ternary': 0,
'new-cap': 2,
'new-parens': 2,
'newline-after-var': 2,
'newline-before-return': 2,
'newline-per-chained-call': 2,
'no-array-constructor': 2,
'no-bitwise': 2,
'no-continue': 2,
'no-inline-comments': 0,
'no-lonely-if': 2,
'no-mixed-operators': 2,
'no-mixed-spaces-and-tabs': 2,
'no-multiple-empty-lines': 2,
'no-negated-condition': 2,
'no-nested-ternary': 0,
'no-new-object': 2,
'no-plusplus': 0,
'no-restricted-syntax': 0,
'no-spaced-func': 2,
'no-tabs': 2,
'no-ternary': 0,
'no-trailing-spaces': 2,
'no-underscore-dangle': 0,
'no-unneeded-ternary': 2,
'no-whitespace-before-property': 2,
'object-curly-newline': 0,
'object-curly-spacing': [ 'error', 'always' ],
'object-property-newline': 2,
'one-var': 0,
'one-var-declaration-per-line': 0,
'operator-assignment': 0,
'operator-linebreak': [ 'error', 'before' ],
'padded-blocks': 0,
'quote-props': 0,
'quotes': [ 'error', 'single' ],
'require-jsdoc': [
'error',
{
'require': {
'ClassDeclaration': true,
'FunctionDeclaration': true,
'MethodDefinition': true
}
}
],
'semi': [ 'error', 'always' ],
'semi-spacing': 2,
'sort-vars': 2,
'space-before-blocks': 2,
'space-before-function-paren': [ 'error', 'never' ],
'space-in-parens': [ 'error', 'never' ],
'space-infix-ops': 2,
'space-unary-ops': 2,
'spaced-comment': 2,
'unicode-bom': 0,
'wrap-regex': 0,
// ES6 group rules // ES6 group rules
'arrow-body-style': [
'error',
'as-needed',
{ requireReturnForObjectLiteral: true }
],
'arrow-parens': [ 'error', 'as-needed' ],
'arrow-spacing': 2,
'constructor-super': 2,
'generator-star-spacing': 2,
'no-class-assign': 2,
'no-confusing-arrow': 2,
'no-const-assign': 2,
'no-dupe-class-members': 2,
'no-new-symbol': 2,
'no-restricted-imports': 0,
'no-this-before-super': 2,
'no-useless-computed-key': 2,
'no-useless-constructor': 2,
'no-useless-rename': 2,
'no-var': 2,
'object-shorthand': [
'error',
'always',
{ 'avoidQuotes': true }
],
'prefer-arrow-callback': [ 'error', { 'allowNamedFunctions': true } ],
'prefer-const': 2,
'prefer-reflect': 0,
'prefer-rest-params': 2,
'prefer-spread': 2,
'prefer-template': 2,
'require-yield': 2,
'rest-spread-spacing': 2,
'sort-imports': 0,
'template-curly-spacing': 2,
'yield-star-spacing': 2,
'import/no-duplicates': 2,
// JsDoc plugin rules group. The following rules are in addition to // JsDoc plugin rules group. The following rules are in addition to
// valid-jsdoc rule. // valid-jsdoc rule.

View File

@ -1,50 +1,58 @@
export default { export default {
NICKNAME_CHANGED: "UI.nickname_changed", NICKNAME_CHANGED: 'UI.nickname_changed',
SELECTED_ENDPOINT: "UI.selected_endpoint", SELECTED_ENDPOINT: 'UI.selected_endpoint',
PINNED_ENDPOINT: "UI.pinned_endpoint", PINNED_ENDPOINT: 'UI.pinned_endpoint',
/** /**
* Notifies that local user created text message. * Notifies that local user created text message.
*/ */
MESSAGE_CREATED: "UI.message_created", MESSAGE_CREATED: 'UI.message_created',
/** /**
* Notifies that local user changed language. * Notifies that local user changed language.
*/ */
LANG_CHANGED: "UI.lang_changed", LANG_CHANGED: 'UI.lang_changed',
/** /**
* Notifies that local user changed email. * Notifies that local user changed email.
*/ */
EMAIL_CHANGED: "UI.email_changed", EMAIL_CHANGED: 'UI.email_changed',
/** /**
* Notifies that "start muted" settings changed. * Notifies that "start muted" settings changed.
*/ */
START_MUTED_CHANGED: "UI.start_muted_changed", START_MUTED_CHANGED: 'UI.start_muted_changed',
AUDIO_MUTED: "UI.audio_muted", AUDIO_MUTED: 'UI.audio_muted',
VIDEO_MUTED: "UI.video_muted", VIDEO_MUTED: 'UI.video_muted',
VIDEO_UNMUTING_WHILE_AUDIO_ONLY: "UI.video_unmuting_while_audio_only", VIDEO_UNMUTING_WHILE_AUDIO_ONLY: 'UI.video_unmuting_while_audio_only',
ETHERPAD_CLICKED: "UI.etherpad_clicked", ETHERPAD_CLICKED: 'UI.etherpad_clicked',
SHARED_VIDEO_CLICKED: "UI.start_shared_video", SHARED_VIDEO_CLICKED: 'UI.start_shared_video',
/** /**
* Updates shared video with params: url, state, time(optional) * Updates shared video with params: url, state, time(optional)
* Where url is the video link, state is stop/start/pause and time is the * Where url is the video link, state is stop/start/pause and time is the
* current video playing time. * current video playing time.
*/ */
UPDATE_SHARED_VIDEO: "UI.update_shared_video", UPDATE_SHARED_VIDEO: 'UI.update_shared_video',
USER_KICKED: "UI.user_kicked", USER_KICKED: 'UI.user_kicked',
REMOTE_AUDIO_MUTED: "UI.remote_audio_muted", REMOTE_AUDIO_MUTED: 'UI.remote_audio_muted',
TOGGLE_FULLSCREEN: "UI.toogle_fullscreen", TOGGLE_FULLSCREEN: 'UI.toogle_fullscreen',
FULLSCREEN_TOGGLED: "UI.fullscreen_toggled", FULLSCREEN_TOGGLED: 'UI.fullscreen_toggled',
AUTH_CLICKED: "UI.auth_clicked", AUTH_CLICKED: 'UI.auth_clicked',
/** /**
* Notifies that the audio only mode was toggled. * Notifies that the audio only mode was toggled.
*/ */
TOGGLE_AUDIO_ONLY: "UI.toggle_audioonly", TOGGLE_AUDIO_ONLY: 'UI.toggle_audioonly',
TOGGLE_CHAT: "UI.toggle_chat", TOGGLE_CHAT: 'UI.toggle_chat',
TOGGLE_SETTINGS: "UI.toggle_settings", TOGGLE_SETTINGS: 'UI.toggle_settings',
TOGGLE_CONTACT_LIST: "UI.toggle_contact_list", TOGGLE_CONTACT_LIST: 'UI.toggle_contact_list',
/** /**
* Notifies that the profile toolbar button has been clicked. * Notifies that the profile toolbar button has been clicked.
*/ */
TOGGLE_PROFILE: "UI.toggle_profile", TOGGLE_PROFILE: 'UI.toggle_profile',
/** /**
* Notifies that a command to toggle the filmstrip has been issued. The * Notifies that a command to toggle the filmstrip has been issued. The
* event may optionally specify a {Boolean} (primitive) value to assign to * event may optionally specify a {Boolean} (primitive) value to assign to
@ -56,7 +64,8 @@ export default {
* *
* @see {TOGGLED_FILMSTRIP} * @see {TOGGLED_FILMSTRIP}
*/ */
TOGGLE_FILMSTRIP: "UI.toggle_filmstrip", TOGGLE_FILMSTRIP: 'UI.toggle_filmstrip',
/** /**
* Notifies that the filmstrip was (actually) toggled. The event supplies a * Notifies that the filmstrip was (actually) toggled. The event supplies a
* {Boolean} (primitive) value indicating the visibility of the filmstrip * {Boolean} (primitive) value indicating the visibility of the filmstrip
@ -64,59 +73,59 @@ export default {
* *
* @see {TOGGLE_FILMSTRIP} * @see {TOGGLE_FILMSTRIP}
*/ */
TOGGLED_FILMSTRIP: "UI.toggled_filmstrip", TOGGLED_FILMSTRIP: 'UI.toggled_filmstrip',
TOGGLE_SCREENSHARING: "UI.toggle_screensharing", TOGGLE_SCREENSHARING: 'UI.toggle_screensharing',
TOGGLED_SHARED_DOCUMENT: "UI.toggled_shared_document", TOGGLED_SHARED_DOCUMENT: 'UI.toggled_shared_document',
CONTACT_CLICKED: "UI.contact_clicked", CONTACT_CLICKED: 'UI.contact_clicked',
HANGUP: "UI.hangup", HANGUP: 'UI.hangup',
LOGOUT: "UI.logout", LOGOUT: 'UI.logout',
RECORDING_TOGGLED: "UI.recording_toggled", RECORDING_TOGGLED: 'UI.recording_toggled',
SUBJECT_CHANGED: "UI.subject_changed", SUBJECT_CHANGED: 'UI.subject_changed',
VIDEO_DEVICE_CHANGED: "UI.video_device_changed", VIDEO_DEVICE_CHANGED: 'UI.video_device_changed',
AUDIO_DEVICE_CHANGED: "UI.audio_device_changed", AUDIO_DEVICE_CHANGED: 'UI.audio_device_changed',
AUDIO_OUTPUT_DEVICE_CHANGED: "UI.audio_output_device_changed", AUDIO_OUTPUT_DEVICE_CHANGED: 'UI.audio_output_device_changed',
/** /**
* Notifies interested listeners that the follow-me feature is enabled or * Notifies interested listeners that the follow-me feature is enabled or
* disabled. * disabled.
*/ */
FOLLOW_ME_ENABLED: "UI.follow_me_enabled", FOLLOW_ME_ENABLED: 'UI.follow_me_enabled',
/** /**
* Notifies that flipX property of the local video is changed. * Notifies that flipX property of the local video is changed.
*/ */
LOCAL_FLIPX_CHANGED: "UI.local_flipx_changed", LOCAL_FLIPX_CHANGED: 'UI.local_flipx_changed',
// An event which indicates that the resolution of a remote video has // An event which indicates that the resolution of a remote video has
// changed. // changed.
RESOLUTION_CHANGED: "UI.resolution_changed", RESOLUTION_CHANGED: 'UI.resolution_changed',
/** /**
* Notifies that the button "Cancel" is pressed on the dialog for * Notifies that the button "Cancel" is pressed on the dialog for
* external extension installation. * external extension installation.
*/ */
EXTERNAL_INSTALLATION_CANCELED: "UI.external_installation_canceled", EXTERNAL_INSTALLATION_CANCELED: 'UI.external_installation_canceled',
/** /**
* Notifies that the side toolbar container has been toggled. The actual * Notifies that the side toolbar container has been toggled. The actual
* event must contain the identifier of the container that has been toggled * event must contain the identifier of the container that has been toggled
* and information about toggle on or off. * 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. * Notifies that the raise hand has been changed.
*/ */
LOCAL_RAISE_HAND_CHANGED: "UI.local_raise_hand_changed", LOCAL_RAISE_HAND_CHANGED: 'UI.local_raise_hand_changed',
/** /**
* Notifies that the avatar is displayed or not on the largeVideo. * Notifies that the avatar is displayed or not on the largeVideo.
*/ */
LARGE_VIDEO_AVATAR_VISIBLE: "UI.large_video_avatar_visible", LARGE_VIDEO_AVATAR_VISIBLE: 'UI.large_video_avatar_visible',
/** /**
* Notifies that the displayed particpant id on the largeVideo is changed. * Notifies that the displayed particpant id on the largeVideo is changed.
*/ */
LARGE_VIDEO_ID_CHANGED: "UI.large_video_id_changed" LARGE_VIDEO_ID_CHANGED: 'UI.large_video_id_changed'
}; };

View File

@ -2,7 +2,7 @@
* The value for the "var" attribute of feature tag in disco-info packets. * The value for the "var" attribute of feature tag in disco-info packets.
*/ */
export const DISCO_REMOTE_CONTROL_FEATURE export const DISCO_REMOTE_CONTROL_FEATURE
= "http://jitsi.org/meet/remotecontrol"; = 'http://jitsi.org/meet/remotecontrol';
/** /**
* Types of remote-control events. * Types of remote-control events.
@ -10,17 +10,17 @@ export const DISCO_REMOTE_CONTROL_FEATURE
* @enum {string} * @enum {string}
*/ */
export const EVENTS = { export const EVENTS = {
mousemove: "mousemove", mousemove: 'mousemove',
mousedown: "mousedown", mousedown: 'mousedown',
mouseup: "mouseup", mouseup: 'mouseup',
mousedblclick: "mousedblclick", mousedblclick: 'mousedblclick',
mousescroll: "mousescroll", mousescroll: 'mousescroll',
keydown: "keydown", keydown: 'keydown',
keyup: "keyup", keyup: 'keyup',
permissions: "permissions", permissions: 'permissions',
start: "start", start: 'start',
stop: "stop", stop: 'stop',
supported: "supported" supported: 'supported'
}; };
/** /**
@ -29,7 +29,7 @@ export const EVENTS = {
* @enum {string} * @enum {string}
*/ */
export const REQUESTS = { export const REQUESTS = {
start: "start" start: 'start'
}; };
/** /**
@ -38,16 +38,16 @@ export const REQUESTS = {
* @enum {string} * @enum {string}
*/ */
export const PERMISSIONS_ACTIONS = { export const PERMISSIONS_ACTIONS = {
request: "request", request: 'request',
grant: "grant", grant: 'grant',
deny: "deny", deny: 'deny',
error: "error" error: 'error'
}; };
/** /**
* The type of remote control messages. * The type of remote control messages.
*/ */
export const REMOTE_CONTROL_MESSAGE_NAME = "remote-control"; export const REMOTE_CONTROL_MESSAGE_NAME = 'remote-control';
/** /**
* The remote control event. * The remote control event.

View File

@ -1,11 +1,13 @@
/* global interfaceConfig */ /* global interfaceConfig */
//list of tips // list of tips
var hints = [ const hints = [
"You can pin participants by clicking on their thumbnails.",// jshint ignore:line 'You can pin participants by clicking on their thumbnails.',
"You can tell others you have something to say by using the \"Raise Hand\" feature",// jshint ignore:line 'You can tell others you have something to say by using the "Raise Hand" '
"You can learn about key shortcuts by pressing Shift+?",// jshint ignore:line + 'feature',
"You can learn more about the state of everyone's connection by hovering on the bars in their thumbnail",// jshint ignore:line 'You can learn about key shortcuts by pressing Shift+?',
"You can hide all thumbnails by using the button in the bottom right corner"// jshint ignore:line 'You can learn more about the state of everyone\'s connection by hovering '
+ 'on the bars in their thumbnail',
'You can hide all thumbnails by using the button in the bottom right corner'
]; ];
/** /**
@ -13,9 +15,9 @@ var hints = [
* *
* @return {string} the hint message. * @return {string} the hint message.
*/ */
function getHint(){ function getHint() {
var l = hints.length - 1; const l = hints.length - 1;
var n = Math.round(Math.random() * l); const n = Math.round(Math.random() * l);
return hints[n]; return hints[n];
} }
@ -27,28 +29,30 @@ function getHint(){
* @param id {string} element identificator * @param id {string} element identificator
* @param msg {string} text message * @param msg {string} text message
*/ */
// eslint-disable-next-line no-unused-vars function insertTextMsg(id, msg) {
function insertTextMsg(id, msg){ const el = document.getElementById(id);
var el = document.getElementById(id);
if (el) if (el) {
el.innerHTML = msg; el.innerHTML = msg;
}
} }
/** /**
* Sets the hint and thanks messages. Will be executed on load event. * Sets the hint and thanks messages. Will be executed on load event.
*/ */
function onLoad() { function onLoad() {
//Works only for close2.html because close.html doesn't have this element. // Works only for close2.html because close.html doesn't have this element.
insertTextMsg('thanksMessage', insertTextMsg('thanksMessage',
'Thank you for using ' + interfaceConfig.APP_NAME); `Thank you for using ${interfaceConfig.APP_NAME}`);
// If there is a setting show a special message only for the guests // If there is a setting show a special message only for the guests
if (interfaceConfig.CLOSE_PAGE_GUEST_HINT) { if (interfaceConfig.CLOSE_PAGE_GUEST_HINT) {
if ( window.sessionStorage.getItem('guest') === 'true' ) { if (window.sessionStorage.getItem('guest') === 'true') {
var element = document.getElementById('hintQuestion'); const element = document.getElementById('hintQuestion');
element.classList.add('hide'); element.classList.add('hide');
insertTextMsg('hintMessage', interfaceConfig.CLOSE_PAGE_GUEST_HINT); insertTextMsg('hintMessage', interfaceConfig.CLOSE_PAGE_GUEST_HINT);
return; return;
} }
} }

View File

@ -3,7 +3,7 @@
const process = require('process'); const process = require('process');
const webpack = require('webpack'); const webpack = require('webpack');
const aui_css = `${__dirname}/node_modules/@atlassian/aui/dist/aui/css/`; const auiCSS = `${__dirname}/node_modules/@atlassian/aui/dist/aui/css/`;
/** /**
* The URL of the Jitsi Meet deployment to be proxy to in the context of * The URL of the Jitsi Meet deployment to be proxy to in the context of
@ -15,6 +15,8 @@ const devServerProxyTarget
const minimize const minimize
= process.argv.indexOf('-p') !== -1 = process.argv.indexOf('-p') !== -1
|| process.argv.indexOf('--optimize-minimize') !== -1; || process.argv.indexOf('--optimize-minimize') !== -1;
// eslint-disable-next-line camelcase
const node_modules = `${__dirname}/node_modules/`; const node_modules = `${__dirname}/node_modules/`;
const plugins = [ const plugins = [
new webpack.LoaderOptionsPlugin({ new webpack.LoaderOptionsPlugin({
@ -62,8 +64,7 @@ const config = {
rules: [ { rules: [ {
// Transpile ES2015 (aka ES6) to ES5. Accept the JSX syntax by React // Transpile ES2015 (aka ES6) to ES5. Accept the JSX syntax by React
// as well. // as well.
exclude: node_modules, // eslint-disable-line camelcase
exclude: node_modules,
loader: 'babel-loader', loader: 'babel-loader',
options: { options: {
// XXX The require.resolve bellow solves failures to locate the // XXX The require.resolve bellow solves failures to locate the
@ -113,10 +114,10 @@ const config = {
// Emit the static assets of AUI such as images that are referenced // Emit the static assets of AUI such as images that are referenced
// by CSS into the output path. // by CSS into the output path.
include: aui_css, include: auiCSS,
loader: 'file-loader', loader: 'file-loader',
options: { options: {
context: aui_css, context: auiCSS,
name: '[path][name].[ext]' name: '[path][name].[ext]'
}, },
test: /\.(gif|png|svg)$/ test: /\.(gif|png|svg)$/
@ -210,9 +211,10 @@ function devServerProxyBypass({ path }) {
const configs = module.exports; const configs = module.exports;
/* eslint-disable indent */ /* eslint-disable indent */
let formattedPath = path;
if ((Array.isArray(configs) ? configs : Array(configs)).some(c => { if ((Array.isArray(configs) ? configs : Array(configs)).some(c => {
if (path.startsWith(c.output.publicPath)) { if (formattedPath.startsWith(c.output.publicPath)) {
if (!minimize) { if (!minimize) {
// Since webpack-dev-server is serving non-minimized // Since webpack-dev-server is serving non-minimized
// artifacts, serve them even if the minimized ones are // artifacts, serve them even if the minimized ones are
@ -220,18 +222,23 @@ function devServerProxyBypass({ path }) {
Object.keys(c.entry).some(e => { Object.keys(c.entry).some(e => {
const name = `${e}.min.js`; const name = `${e}.min.js`;
if (path.indexOf(name) !== -1) { if (formattedPath.indexOf(name) !== -1) {
path = path.replace(name, `${e}.js`); formattedPath
= formattedPath.replace(name, `${e}.js`);
return true; return true;
} }
return false;
}); });
} }
return true; return true;
} }
return false;
})) { })) {
return path; return formattedPath;
} }
/* eslint-enable indent */ /* eslint-enable indent */