From 6b98fd52ea2e280d41a28ecd8e59106eb6a0090f Mon Sep 17 00:00:00 2001 From: isymchych Date: Fri, 15 Jan 2016 16:59:35 +0200 Subject: [PATCH] added some documentation (jsdocs) --- conference.js | 77 ++++++++-- connection.js | 25 ++++ modules/UI/UI.js | 178 ++++++++++++++++++++++- modules/UI/authentication/AuthHandler.js | 24 +++ modules/UI/authentication/LoginDialog.js | 62 +++++++- modules/UI/authentication/RoomLocker.js | 52 ++++++- modules/UI/etherpad/Etherpad.js | 22 +++ modules/UI/prezi/Prezi.js | 71 +++++++++ modules/UI/videolayout/LargeContainer.js | 17 +++ modules/UI/videolayout/LargeVideo.js | 87 ++++++++++- modules/UI/videolayout/VideoLayout.js | 2 +- 11 files changed, 590 insertions(+), 27 deletions(-) diff --git a/conference.js b/conference.js index d75b11ec6..e2859f87a 100644 --- a/conference.js +++ b/conference.js @@ -17,6 +17,9 @@ const ConferenceErrors = JitsiMeetJS.errors.conference; let room, connection, localTracks, localAudio, localVideo, roomLocker; +/** + * Known custom conference commands. + */ const Commands = { CONNECTION_QUALITY: "stats", EMAIL: "email", @@ -26,6 +29,10 @@ const Commands = { STOP_PREZI: "stop-prezi" }; +/** + * Open Connection. When authentication failed it shows auth dialog. + * @returns Promise + */ function connect() { return openConnection({retry: true}).catch(function (err) { if (err === ConnectionErrors.PASSWORD_REQUIRED) { @@ -37,8 +44,14 @@ function connect() { }); } -const addTrack = (track) => { +/** + * Add local track to the conference and shares + * video type with other users if its video track. + * @param {JitsiLocalTrack} track local track + */ +function addTrack (track) { room.addTrack(track); + if (track.isAudioTrack()) { return; } @@ -50,25 +63,35 @@ const addTrack = (track) => { xmlns: 'http://jitsi.org/jitmeet/video' } }); -}; +} -// share email with other users -const sendEmail = (email) => { +/** + * Share email with other users. + * @param {string} email new email + */ +function sendEmail (email) { room.sendCommand(Commands.EMAIL, { value: email, attributes: { id: room.myUserId() } }); -}; +} - -const unload = () => { +/** + * Leave the conference and close connection. + */ +function unload () { room.leave(); connection.disconnect(); -}; +} -const getDisplayName = (id) => { +/** + * Get user nickname by user id. + * @param {string} id user id + * @returns {string?} user nickname or undefined if user is unknown. + */ +function getDisplayName (id) { if (APP.conference.isLocalId(id)) { return APP.settings.getDisplayName(); } @@ -77,7 +100,7 @@ const getDisplayName = (id) => { if (participant && participant.getDisplayName()) { return participant.getDisplayName(); } -}; +} class ConferenceConnector { constructor(resolve, reject) { @@ -151,6 +174,12 @@ export default { isModerator: false, audioMuted: false, videoMuted: false, + /** + * Open new connection and join to the conference. + * @param {object} options + * @param {string} roomName name of the conference + * @returns {Promise} + */ init(options) { this.roomName = options.roomName; JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.TRACE); @@ -173,6 +202,12 @@ export default { }); }); }, + /** + * Create local tracks of specified types. + * If we cannot obtain required tracks it will return empty array. + * @param {string[]} devices required track types ('audio', 'video' etc.) + * @returns {Promise} + */ createLocalTracks (...devices) { return JitsiMeetJS.createLocalTracks({ // copy array to avoid mutations inside library @@ -184,6 +219,11 @@ export default { return Promise.reject(err); }); }, + /** + * Check if id is id of the local user. + * @param {string} id id to check + * @returns {boolean} + */ isLocalId (id) { return this.localId === id; }, @@ -217,12 +257,24 @@ export default { toggleVideoMuted () { this.muteVideo(!this.videoMuted); }, + /** + * Retrieve list of conference participants (without local user). + * @returns {JitsiParticipant[]} + */ listMembers () { return room.getParticipants(); }, + /** + * Retrieve list of ids of conference participants (without local user). + * @returns {string[]} + */ listMembersIds () { return room.getParticipants().map(p => p.getId()); }, + /** + * Check if SIP is supported. + * @returns {boolean} + */ sipGatewayEnabled () { return room.isSIPCallingSupported(); }, @@ -279,7 +331,7 @@ export default { /** * Will check for number of remote particiapnts that have at least one * remote track. - * @return boolean whether we have enough participants with remote streams + * @return {boolean} whether we have enough participants with remote streams */ checkEnoughParticipants (number) { var participants = this._room.getParticipants(); @@ -336,6 +388,9 @@ export default { } return options; }, + /** + * Setup interaction between conference and UI. + */ _setupListeners () { // add local streams when joined to the conference room.on(ConferenceEvents.CONFERENCE_JOINED, () => { diff --git a/connection.js b/connection.js index 91ddb89a8..0fd775bc1 100644 --- a/connection.js +++ b/connection.js @@ -5,6 +5,13 @@ import LoginDialog from './modules/UI/authentication/LoginDialog'; const ConnectionEvents = JitsiMeetJS.events.connection; const ConnectionErrors = JitsiMeetJS.errors.connection; +/** + * Try to open connection using provided credentials. + * @param {string} [id] + * @param {string} [password] + * @returns {Promise} connection if + * everything is ok, else error. + */ function connect(id, password) { let connection = new JitsiMeetJS.JitsiConnection(null, null, config); @@ -42,6 +49,12 @@ function connect(id, password) { }); } +/** + * Show Authentication Dialog and try to connect with new credentials. + * If failed to connect because of PASSWORD_REQUIRED error + * then ask for password again. + * @returns {Promise} + */ function requestAuth() { return new Promise(function (resolve, reject) { let authDialog = LoginDialog.showAuthDialog( @@ -62,6 +75,18 @@ function requestAuth() { }); } +/** + * Open JitsiConnection using provided credentials. + * If retry option is true it will show auth dialog on PASSWORD_REQUIRED error. + * + * @param {object} options + * @param {string} [options.id] + * @param {string} [options.password] + * @param {boolean} [retry] if we should show auth dialog + * on PASSWORD_REQUIRED error. + * + * @returns {Promise} + */ export function openConnection({id, password, retry}) { return connect(id, password).catch(function (err) { if (!retry) { diff --git a/modules/UI/UI.js b/modules/UI/UI.js index caa424da5..c5c2d666d 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -31,6 +31,9 @@ UI.eventEmitter = eventEmitter; let preziManager; let etherpadManager; +/** + * Prompt user for nickname. + */ function promptDisplayName() { let nickRequiredMsg = APP.translation.translateString("dialog.displayNameRequired"); let defaultNickMsg = APP.translation.translateString( @@ -76,6 +79,9 @@ function promptDisplayName() { ); } +/** + * Initialize chat. + */ function setupChat() { Chat.init(eventEmitter); $("#toggle_smileys").click(function() { @@ -83,6 +89,9 @@ function setupChat() { }); } +/** + * Initialize toolbars. + */ function setupToolbars() { Toolbar.init(eventEmitter); Toolbar.setupButtonsFromConfig(); @@ -113,6 +122,9 @@ function toggleFullScreen () { } } +/** + * Notify user that server has shut down. + */ UI.notifyGracefulShudown = function () { messageHandler.openMessageDialog( 'dialog.serviceUnavailable', @@ -120,6 +132,9 @@ UI.notifyGracefulShudown = function () { ); }; +/** + * Notify user that reservation error happened. + */ UI.notifyReservationError = function (code, msg) { var title = APP.translation.generateTranslationHTML( "dialog.reservationError"); @@ -135,14 +150,25 @@ UI.notifyReservationError = function (code, msg) { ); }; +/** + * Notify user that he has been kicked from the server. + */ UI.notifyKicked = function () { messageHandler.openMessageDialog("dialog.sessTerminated", "dialog.kickMessage"); }; +/** + * Notify user that Jitsi Videobridge is not accessible. + */ UI.notifyBridgeDown = function () { messageHandler.showError("dialog.error", "dialog.bridgeUnavailable"); }; +/** + * Change nickname for the user. + * @param {string} id user id + * @param {string} displayName new nickname + */ UI.changeDisplayName = function (id, displayName) { ContactList.onDisplayNameChange(id, displayName); SettingsMenu.onDisplayNameChange(id, displayName); @@ -153,6 +179,9 @@ UI.changeDisplayName = function (id, displayName) { } }; +/** + * Intitialize conference UI. + */ UI.initConference = function () { var id = APP.conference.localId; Toolbar.updateRoomUrl(window.location.href); @@ -186,6 +215,9 @@ UI.mucJoined = function () { VideoLayout.mucJoined(); }; +/** + * Setup some UI event listeners. + */ function registerListeners() { UI.addListener(UIEvents.EMAIL_CHANGED, function (email) { UI.setUserAvatar(APP.conference.localId, email); @@ -214,6 +246,9 @@ function registerListeners() { UI.addListener(UIEvents.TOGGLE_FILM_STRIP, UI.toggleFilmStrip); } +/** + * Setup some DOM event listeners. + */ function bindEvents() { function onResize() { PanelToggler.resizeChat(); @@ -341,6 +376,10 @@ UI.start = function () { }; +/** + * Show local stream on UI. + * @param {JitsiTrack} track stream to show + */ UI.addLocalStream = function (track) { switch (track.getType()) { case 'audio': @@ -356,18 +395,30 @@ UI.addLocalStream = function (track) { }; -UI.addRemoteStream = function (stream) { - VideoLayout.onRemoteStreamAdded(stream); +/** + * Show remote stream on UI. + * @param {JitsiTrack} track stream to show + */ +UI.addRemoteStream = function (track) { + VideoLayout.onRemoteStreamAdded(track); }; function chatAddError(errorMessage, originalText) { return Chat.chatAddError(errorMessage, originalText); } +/** + * Update chat subject. + * @param {string} subject new chat subject + */ UI.setSubject = function (subject) { Chat.setSubject(subject); }; +/** + * Setup and show Etherpad. + * @param {string} name etherpad id + */ UI.initEtherpad = function (name) { if (etherpadManager || !config.etherpad_base || !name) { return; @@ -377,6 +428,11 @@ UI.initEtherpad = function (name) { Toolbar.showEtherpadButton(); }; +/** + * Show user on UI. + * @param {string} id user id + * @param {string} displayName user nickname + */ UI.addUser = function (id, displayName) { ContactList.addContact(id); @@ -395,6 +451,11 @@ UI.addUser = function (id, displayName) { VideoLayout.addParticipantContainer(id); }; +/** + * Remove user from UI. + * @param {string} id user id + * @param {string} displayName user nickname + */ UI.removeUser = function (id, displayName) { ContactList.removeContact(id); @@ -415,10 +476,19 @@ UI.removeUser = function (id, displayName) { // VideoLayout.setPresenceStatus(Strophe.getResourceFromJid(jid), info.status); // } +/** + * Update videotype for specified user. + * @param {string} id user id + * @param {string} newVideoType new videotype + */ UI.onPeerVideoTypeChanged = (id, newVideoType) => { VideoLayout.onVideoTypeChanged(id, newVideoType); }; +/** + * Update local user role and show notification if user is moderator. + * @param {boolean} isModerator if local user is moderator or not + */ UI.updateLocalRole = function (isModerator) { VideoLayout.showModeratorIndicator(); @@ -462,22 +532,38 @@ UI.updateUserRole = function (user) { }; +/** + * Toggles smileys in the chat. + */ UI.toggleSmileys = function () { Chat.toggleSmileys(); }; +/** + * Get current settings. + * @returns {object} settings + */ UI.getSettings = function () { return Settings.getSettings(); }; +/** + * Toggles film strip. + */ UI.toggleFilmStrip = function () { BottomToolbar.toggleFilmStrip(); }; +/** + * Toggles chat panel. + */ UI.toggleChat = function () { PanelToggler.toggleChat(); }; +/** + * Toggles contact list panel. + */ UI.toggleContactList = function () { PanelToggler.toggleContactList(); }; @@ -499,6 +585,7 @@ UI.connectionIndicatorShowMore = function(jid) { return VideoLayout.showMore(jid); }; +// FIXME check if someone user this UI.showLoginPopup = function(callback) { console.log('password is required'); var message = '

'; @@ -569,6 +656,11 @@ UI.dockToolbar = function (isDock) { ToolbarToggler.dockToolbar(isDock); }; +/** + * Update user avatar. + * @param {string} id user id + * @param {stirng} email user email + */ UI.setUserAvatar = function (id, email) { // update avatar Avatar.setUserAvatar(id, email); @@ -582,6 +674,10 @@ UI.setUserAvatar = function (id, email) { } }; +/** + * Notify user that connection failed. + * @param {string} stropheErrorMsg raw Strophe error message + */ UI.notifyConnectionFailed = function (stropheErrorMsg) { var title = APP.translation.generateTranslationHTML( "dialog.error"); @@ -600,6 +696,10 @@ UI.notifyConnectionFailed = function (stropheErrorMsg) { ); }; +/** + * Notify user that he need to install Firefox extension to share screen. + * @param {stirng} url extension url + */ UI.notifyFirefoxExtensionRequired = function (url) { messageHandler.openMessageDialog( "dialog.extensionRequired", @@ -611,12 +711,19 @@ UI.notifyFirefoxExtensionRequired = function (url) { ); }; +/** + * Notify user that he was automatically muted when joned the conference. + */ UI.notifyInitiallyMuted = function () { messageHandler.notify( null, "notify.mutedTitle", "connected", "notify.muted", null, {timeOut: 120000} ); }; +/** + * Mark user as dominant speaker. + * @param {string} id user id + */ UI.markDominantSpeaker = function (id) { VideoLayout.onDominantSpeakerChanged(id); }; @@ -625,26 +732,53 @@ UI.handleLastNEndpoints = function (ids) { VideoLayout.onLastNEndpointsChanged(ids, []); }; +/** + * Update audio level visualization for specified user. + * @param {string} id user id + * @param {number} lvl audio level + */ UI.setAudioLevel = function (id, lvl) { VideoLayout.setAudioLevel(id, lvl); }; +/** + * Update state of desktop sharing buttons. + * @param {boolean} isSharingScreen if user is currently sharing his screen + */ UI.updateDesktopSharingButtons = function (isSharingScreen) { Toolbar.changeDesktopSharingButtonState(isSharingScreen); }; +/** + * Hide connection quality statistics from UI. + */ UI.hideStats = function () { VideoLayout.hideStats(); }; +/** + * Update local connection quality statistics. + * @param {number} percent + * @param {object} stats + */ UI.updateLocalStats = function (percent, stats) { VideoLayout.updateLocalConnectionStats(percent, stats); }; +/** + * Update connection quality statistics for remote user. + * @param {string} id user id + * @param {number} percent + * @param {object} stats + */ UI.updateRemoteStats = function (id, percent, stats) { VideoLayout.updateConnectionStats(id, percent, stats); }; +/** + * Mark video as interrupted or not. + * @param {boolean} interrupted if video is interrupted + */ UI.markVideoInterrupted = function (interrupted) { if (interrupted) { VideoLayout.onVideoInterrupted(); @@ -653,6 +787,10 @@ UI.markVideoInterrupted = function (interrupted) { } }; +/** + * Mark room as locked or not. + * @param {boolean} locked if room is locked. + */ UI.markRoomLocked = function (locked) { if (locked) { Toolbar.lockLockButton(); @@ -661,6 +799,13 @@ UI.markRoomLocked = function (locked) { } }; +/** + * Add chat message. + * @param {string} from user id + * @param {string} displayName user nickname + * @param {string} message message text + * @param {number} stamp timestamp when message was created + */ UI.addMessage = function (from, displayName, message, stamp) { Chat.updateChatConversation(from, displayName, message, stamp); }; @@ -672,6 +817,10 @@ UI.updateDTMFSupport = function (isDTMFSupported) { /** * Invite participants to conference. + * @param {string} roomUrl + * @param {string} conferenceName + * @param {string} key + * @param {string} nick */ UI.inviteParticipants = function (roomUrl, conferenceName, key, nick) { let keyText = ""; @@ -710,6 +859,10 @@ UI.inviteParticipants = function (roomUrl, conferenceName, key, nick) { window.open(`mailto:?subject=${subject}&body=${body}`, '_blank'); }; +/** + * Show user feedback dialog if its required or just show "thank you" dialog. + * @returns {Promise} when dialog is closed. + */ UI.requestFeedback = function () { return new Promise(function (resolve, reject) { if (Feedback.isEnabled()) { @@ -736,6 +889,10 @@ UI.requestFeedback = function () { }); }; +/** + * Request recording token from the user. + * @returns {Promise} + */ UI.requestRecordingToken = function () { let msg = APP.translation.generateTranslationHTML("dialog.recordingToken"); let token = APP.translation.translateString("dialog.token"); @@ -769,6 +926,11 @@ UI.notifyTokenAuthFailed = function () { messageHandler.showError("dialog.error", "dialog.tokenAuthFailed"); }; +/** + * Updates auth info on the UI. + * @param {boolean} isAuthEnabled if authentication is enabled + * @param {string} [login] current login + */ UI.updateAuthInfo = function (isAuthEnabled, login) { let loggedIn = !!login; @@ -782,10 +944,20 @@ UI.updateAuthInfo = function (isAuthEnabled, login) { } }; +/** + * Show Prezi from the user. + * @param {string} userId user id + * @param {string} url Prezi url + * @param {number} slide slide to show + */ UI.showPrezi = function (userId, url, slide) { preziManager.showPrezi(userId, url, slide); }; +/** + * Stop showing Prezi from the user. + * @param {string} userId user id + */ UI.stopPrezi = function (userId) { if (preziManager.isSharing(userId)) { preziManager.removePrezi(userId); @@ -798,7 +970,7 @@ UI.onStartMutedChanged = function () { /** * Returns the id of the current video shown on large. - * Currently used by tests (troture). + * Currently used by tests (torture). */ UI.getLargeVideoID = function () { return VideoLayout.getLargeVideoID(); diff --git a/modules/UI/authentication/AuthHandler.js b/modules/UI/authentication/AuthHandler.js index 70d336d17..8521b65d1 100644 --- a/modules/UI/authentication/AuthHandler.js +++ b/modules/UI/authentication/AuthHandler.js @@ -10,6 +10,13 @@ const ConferenceEvents = JitsiMeetJS.events.conference; let externalAuthWindow; let authRequiredDialog; +/** + * Authenticate using external service or just focus + * external auth window if there is one already. + * + * @param {JitsiConference} room + * @param {string} [lockPassword] password to use if the conference is locked + */ function doExternalAuth (room, lockPassword) { if (externalAuthWindow) { externalAuthWindow.focus(); @@ -32,6 +39,11 @@ function doExternalAuth (room, lockPassword) { } } +/** + * Authenticate on the server. + * @param {JitsiConference} room + * @param {string} [lockPassword] password to use if the conference is locked + */ function doXmppAuth (room, lockPassword) { let loginDialog = LoginDialog.showAuthDialog(function (id, password) { // auth "on the fly": @@ -83,6 +95,12 @@ function doXmppAuth (room, lockPassword) { }); } +/** + * Authenticate for the conference. + * Uses external service for auth if conference supports that. + * @param {JitsiConference} room + * @param {string} [lockPassword] password to use if the conference is locked + */ function authenticate (room, lockPassword) { if (room.isExternalAuthEnabled()) { doExternalAuth(room, lockPassword); @@ -91,6 +109,9 @@ function authenticate (room, lockPassword) { } } +/** + * Notify user that authentication is required to create the conference. + */ function requireAuth(roomName) { if (authRequiredDialog) { return; @@ -101,6 +122,9 @@ function requireAuth(roomName) { ); } +/** + * Close auth-related dialogs if there are any. + */ function closeAuth() { if (externalAuthWindow) { externalAuthWindow.close(); diff --git a/modules/UI/authentication/LoginDialog.js b/modules/UI/authentication/LoginDialog.js index 8c5b8a944..30488cb0a 100644 --- a/modules/UI/authentication/LoginDialog.js +++ b/modules/UI/authentication/LoginDialog.js @@ -2,6 +2,10 @@ var messageHandler = require('../util/MessageHandler'); +/** + * Build html for "password required" dialog. + * @returns {string} html string + */ function getPasswordInputHtml() { let placeholder = config.hosts.authdomain ? "user identity" @@ -13,11 +17,16 @@ function getPasswordInputHtml() {

${passRequiredMsg}

+ data-i18n="[placeholder]dialog.userPassword" + placeholder="user password"> `; } +/** + * Convert provided id to jid if it's not jid yet. + * @param {string} id user id or jid + * @returns {string} jid + */ function toJid(id) { if (id.indexOf("@") >= 0) { return id; @@ -33,6 +42,10 @@ function toJid(id) { return jid; } +/** + * Generate cancel button config for the dialog. + * @returns {Object} + */ function cancelButton() { return { title: APP.translation.generateTranslationHTML("dialog.Cancel"), @@ -40,7 +53,18 @@ function cancelButton() { }; } -function Dialog(successCallback, cancelCallback) { +/** + * Auth dialog for JitsiConnection which supports retries. + * If no cancelCallback provided then there will be + * no cancel button on the dialog. + * + * @class LoginDialog + * @constructor + * + * @param {function(jid, password)} successCallback + * @param {function} [cancelCallback] callback to invoke if user canceled. + */ +function LoginDialog(successCallback, cancelCallback) { let loginButtons = [{ title: APP.translation.generateTranslationHTML("dialog.Ok"), value: true @@ -118,6 +142,10 @@ function Dialog(successCallback, cancelCallback) { connDialog.goToState('finished'); }; + /** + * Show message as connection status. + * @param {string} message + */ this.displayConnectionStatus = function (message) { let connectingState = connDialog.getState('connecting'); @@ -133,12 +161,26 @@ function Dialog(successCallback, cancelCallback) { }; } -const LoginDialog = { +export default { + /** + * Show new auth dialog for JitsiConnection. + * + * @param {function(jid, password)} successCallback + * @param {function} [cancelCallback] callback to invoke if user canceled. + * + * @returns {LoginDialog} + */ showAuthDialog: function (successCallback, cancelCallback) { - return new Dialog(successCallback, cancelCallback); + return new LoginDialog(successCallback, cancelCallback); }, + /** + * Show notification that external auth is required (using provided url). + * @param {string} url URL to use for external auth. + * @param {function} callback callback to invoke when auth popup is closed. + * @returns auth dialog + */ showExternalAuthDialog: function (url, callback) { var dialog = messageHandler.openCenteredPopup( url, 910, 660, @@ -153,6 +195,14 @@ const LoginDialog = { return dialog; }, + /** + * Show notification that authentication is required + * to create the conference, so he should authenticate or wait for a host. + * @param {string} roomName name of the conference + * @param {function} onAuthNow callback to invoke if + * user want to authenticate. + * @returns dialog + */ showAuthRequiredDialog: function (roomName, onAuthNow) { var title = APP.translation.generateTranslationHTML( "dialog.WaitingForHost" @@ -184,5 +234,3 @@ const LoginDialog = { ); } }; - -export default LoginDialog; diff --git a/modules/UI/authentication/RoomLocker.js b/modules/UI/authentication/RoomLocker.js index 18eb678d7..6f083483f 100644 --- a/modules/UI/authentication/RoomLocker.js +++ b/modules/UI/authentication/RoomLocker.js @@ -4,6 +4,10 @@ import UIUtil from '../util/UIUtil'; //FIXME: import AnalyticsAdapter from '../../statistics/AnalyticsAdapter'; +/** + * Show dialog which asks user for new password for the conference. + * @returns {Promise} password or nothing if user canceled + */ function askForNewPassword () { let passMsg = APP.translation.generateTranslationHTML("dialog.passwordMsg"); let yourPassMsg = APP.translation.translateString("dialog.yourPassword"); @@ -30,6 +34,10 @@ function askForNewPassword () { }); } +/** + * Show dialog which asks for required conference password. + * @returns {Promise} password or nothing if user canceled + */ function askForPassword () { let passRequiredMsg = APP.translation.translateString( "dialog.passwordRequired" @@ -58,6 +66,10 @@ function askForPassword () { }); } +/** + * Show dialog which asks if user want remove password from the conference. + * @returns {Promise} + */ function askToUnlock () { return new Promise(function (resolve, reject) { messageHandler.openTwoButtonDialog( @@ -74,18 +86,32 @@ function askToUnlock () { }); } -function notifyPasswordNotSupported (err) { - console.warn('setting password failed', err); +/** + * Show notification that user cannot set password for the conference + * because server doesn't support that. + */ +function notifyPasswordNotSupported () { + console.warn('room passwords not supported'); messageHandler.showError("dialog.warning", "dialog.passwordNotSupported"); } -function notifyPasswordFailed() { - console.warn('room passwords not supported'); +/** + * Show notification that setting password for the conference failed. + * @param {Error} err error + */ +function notifyPasswordFailed(err) { + console.warn('setting password failed', err); messageHandler.showError("dialog.lockTitle", "dialog.lockMessage"); } const ConferenceErrors = JitsiMeetJS.errors.conference; +/** + * Create new RoomLocker for the conference. + * It allows to set or remove password for the conference, + * or ask for required password. + * @returns {RoomLocker} + */ export default function createRoomLocker (room) { let password; @@ -103,6 +129,9 @@ export default function createRoomLocker (room) { }); } + /** + * @class RoomLocker + */ return { get isLocked () { return !!password; @@ -112,6 +141,10 @@ export default function createRoomLocker (room) { return password; }, + /** + * Allows to remove password from the conference (asks user first). + * @returns {Promise} + */ askToUnlock () { return askToUnlock().then(function () { return lock(); @@ -120,6 +153,11 @@ export default function createRoomLocker (room) { }); }, + /** + * Allows to set password for the conference. + * It asks user for new password and locks the room. + * @returns {Promise} + */ askToLock () { return askForNewPassword().then(function (newPass) { return lock(newPass); @@ -128,12 +166,18 @@ export default function createRoomLocker (room) { }); }, + /** + * Asks user for required conference password. + */ requirePassword () { return askForPassword().then(function (newPass) { password = newPass; }); }, + /** + * Show notification that to set/remove password user must be moderator. + */ notifyModeratorRequired () { if (password) { messageHandler.openMessageDialog(null, "dialog.passwordError"); diff --git a/modules/UI/etherpad/Etherpad.js b/modules/UI/etherpad/Etherpad.js index 45c172f74..1ae49289f 100644 --- a/modules/UI/etherpad/Etherpad.js +++ b/modules/UI/etherpad/Etherpad.js @@ -6,6 +6,9 @@ import UIUtil from "../util/UIUtil"; import SidePanelToggler from "../side_pannels/SidePanelToggler"; import BottomToolbar from '../toolbars/BottomToolbar'; +/** + * Etherpad options. + */ const options = $.param({ showControns: true, showChat: false, @@ -40,11 +43,20 @@ function bubbleIframeMouseMove(iframe){ }; } +/** + * Default Etherpad frame width. + */ const DEFAULT_WIDTH = 640; +/** + * Default Etherpad frame height. + */ const DEFAULT_HEIGHT = 480; const EtherpadContainerType = "etherpad"; +/** + * Container for Etherpad iframe. + */ class Etherpad extends LargeContainer { constructor (domain, name) { super(); @@ -123,6 +135,9 @@ class Etherpad extends LargeContainer { } } +/** + * Manager of the Etherpad frame. + */ export default class EtherpadManager { constructor (domain, name) { if (!domain || !name) { @@ -138,6 +153,9 @@ export default class EtherpadManager { return !!this.etherpad; } + /** + * Create new Etherpad frame. + */ openEtherpad () { this.etherpad = new Etherpad(this.domain, this.name); VideoLayout.addLargeVideoContainer( @@ -146,6 +164,10 @@ export default class EtherpadManager { ); } + /** + * Toggle Etherpad frame visibility. + * Open new Etherpad frame if there is no Etherpad frame yet. + */ toggleEtherpad () { if (!this.isOpen) { this.openEtherpad(); diff --git a/modules/UI/prezi/Prezi.js b/modules/UI/prezi/Prezi.js index 0f3d4e5f2..c9526b891 100644 --- a/modules/UI/prezi/Prezi.js +++ b/modules/UI/prezi/Prezi.js @@ -11,17 +11,31 @@ import ToolbarToggler from "../toolbars/ToolbarToggler"; import SidePanelToggler from "../side_pannels/SidePanelToggler"; import BottomToolbar from '../toolbars/BottomToolbar'; +/** + * Example of Prezi link. + */ const defaultPreziLink = "http://prezi.com/wz7vhjycl7e6/my-prezi"; const alphanumRegex = /^[a-z0-9-_\/&\?=;]+$/i; +/** + * Default aspect ratio for Prezi frame. + */ const aspectRatio = 16.0 / 9.0; +/** + * Default Prezi frame width. + */ const DEFAULT_WIDTH = 640; +/** + * Default Prezi frame height. + */ const DEFAULT_HEIGHT = 480; /** * Indicates if the given string is an alphanumeric string. * Note that some special characters are also allowed (-, _ , /, &, ?, =, ;) for the * purpose of checking URIs. + * @param {string} unsafeText string to check + * @returns {boolean} */ function isAlphanumeric(unsafeText) { return alphanumRegex.test(unsafeText); @@ -29,12 +43,19 @@ function isAlphanumeric(unsafeText) { /** * Returns the presentation id from the given url. + * @param {string} url Prezi link + * @returns {string} presentation id */ function getPresentationId (url) { let presId = url.substring(url.indexOf("prezi.com/") + 10); return presId.substring(0, presId.indexOf('/')); } +/** + * Checks if given string is Prezi url. + * @param {string} url string to check. + * @returns {boolean} + */ function isPreziLink(url) { if (url.indexOf('http://prezi.com/') !== 0 && url.indexOf('https://prezi.com/') !== 0) { return false; @@ -48,6 +69,9 @@ function isPreziLink(url) { return true; } +/** + * Notify user that other user if already sharing Prezi. + */ function notifyOtherIsSharingPrezi() { messageHandler.openMessageDialog( "dialog.sharePreziTitle", @@ -55,6 +79,9 @@ function notifyOtherIsSharingPrezi() { ); } +/** + * Ask user if he want to close Prezi he's sharing. + */ function proposeToClosePrezi() { return new Promise(function (resolve, reject) { messageHandler.openTwoButtonDialog( @@ -76,6 +103,10 @@ function proposeToClosePrezi() { }); } +/** + * Ask user for Prezi url to share with others. + * Dialog validates client input to allow only Prezi urls. + */ function requestPreziLink() { const title = APP.translation.generateTranslationHTML("dialog.sharePreziTitle"); const cancelButton = APP.translation.generateTranslationHTML("dialog.Cancel"); @@ -154,6 +185,9 @@ function requestPreziLink() { export const PreziContainerType = "prezi"; +/** + * Container for Prezi iframe. + */ class PreziContainer extends LargeContainer { constructor ({preziId, isMy, slide, onSlideChanged}) { @@ -187,6 +221,10 @@ class PreziContainer extends LargeContainer { }); } + /** + * Change Prezi slide. + * @param {number} slide slide to show + */ goToSlide (slide) { if (this.preziPlayer.getCurrentStep() === slide) { return; @@ -204,6 +242,10 @@ class PreziContainer extends LargeContainer { } } + /** + * Show or hide "reload presentation" button. + * @param {boolean} show + */ showReloadBtn (show) { this.reloadBtn.css('display', show ? 'inline-block' : 'none'); } @@ -256,6 +298,9 @@ class PreziContainer extends LargeContainer { this.$iframe.width(width).height(height); } + /** + * Close Prezi frame. + */ close () { this.showReloadBtn(false); this.preziPlayer.destroy(); @@ -263,6 +308,9 @@ class PreziContainer extends LargeContainer { } } +/** + * Manager of Prezi frames. + */ export default class PreziManager { constructor (emitter) { this.emitter = emitter; @@ -282,6 +330,10 @@ export default class PreziManager { return this.userId === APP.conference.localId; } + /** + * Check if user is currently sharing. + * @param {string} id user id to check for + */ isSharing (id) { return this.userId === id; } @@ -302,6 +354,9 @@ export default class PreziManager { } } + /** + * Reload current Prezi frame. + */ reloadPresentation () { if (!this.prezi) { return; @@ -310,6 +365,12 @@ export default class PreziManager { iframe.src = iframe.src; } + /** + * Show Prezi. Create new Prezi if there is no Prezi yet. + * @param {string} id owner id + * @param {string} url Prezi url + * @param {number} slide slide to show + */ showPrezi (id, url, slide) { if (!this.isPresenting) { this.createPrezi(id, url, slide); @@ -324,6 +385,12 @@ export default class PreziManager { } } + /** + * Create new Prezi frame.. + * @param {string} id owner id + * @param {string} url Prezi url + * @param {number} slide slide to show + */ createPrezi (id, url, slide) { console.log("presentation added", url); @@ -354,6 +421,10 @@ export default class PreziManager { VideoLayout.showLargeVideoContainer(PreziContainerType, true); } + /** + * Close Prezi. + * @param {string} id owner id + */ removePrezi (id) { if (this.userId !== id) { throw new Error(`cannot close presentation from ${this.userId} instead of ${id}`); diff --git a/modules/UI/videolayout/LargeContainer.js b/modules/UI/videolayout/LargeContainer.js index b74ac50a4..319ccef62 100644 --- a/modules/UI/videolayout/LargeContainer.js +++ b/modules/UI/videolayout/LargeContainer.js @@ -1,24 +1,41 @@ +/** + * Base class for all Large containers which we can show. + */ export default class LargeContainer { /** + * Show this container. * @returns Promise */ show () { } /** + * Hide this container. * @returns Promise */ hide () { } + /** + * Resize this container. + * @param {number} containerWidth available width + * @param {number} containerHeight available height + * @param {boolean} animate if container should animate it's resize process + */ resize (containerWidth, containerHeight, animate) { } + /** + * Handler for "hover in" events. + */ onHoverIn (e) { } + /** + * Handler for "hover out" events. + */ onHoverOut (e) { } } diff --git a/modules/UI/videolayout/LargeVideo.js b/modules/UI/videolayout/LargeVideo.js index 05783cdbc..5353a8cf6 100644 --- a/modules/UI/videolayout/LargeVideo.js +++ b/modules/UI/videolayout/LargeVideo.js @@ -11,6 +11,10 @@ const RTCBrowserType = require("../../RTC/RTCBrowserType"); const avatarSize = interfaceConfig.DOMINANT_SPEAKER_AVATAR_SIZE; +/** + * Get stream id. + * @param {JitsiTrack?} stream + */ function getStreamId(stream) { if(!stream) return; @@ -147,6 +151,9 @@ function getDesktopVideoPosition(videoWidth, export const VideoContainerType = "video"; +/** + * Container for user video. + */ class VideoContainer extends LargeContainer { // FIXME: With Temasys we have to re-select everytime get $video () { @@ -174,6 +181,10 @@ class VideoContainer extends LargeContainer { this.$video.on('play', onPlay); } + /** + * Get size of video element. + * @returns {{width, height}} + */ getStreamSize () { let video = this.$video[0]; return { @@ -182,6 +193,12 @@ class VideoContainer extends LargeContainer { }; } + /** + * Calculate optimal video size for specified container size. + * @param {number} containerWidth container width + * @param {number} containerHeight container height + * @returns {{availableWidth, availableHeight}} + */ getVideoSize (containerWidth, containerHeight) { let { width, height } = this.getStreamSize(); if (this.stream && this.isScreenSharing()) { @@ -197,6 +214,15 @@ class VideoContainer extends LargeContainer { } } + /** + * Calculate optimal video position (offset for top left corner) + * for specified video size and container size. + * @param {number} width video width + * @param {number} height video height + * @param {number} containerWidth container width + * @param {number} containerHeight container height + * @returns {{horizontalIndent, verticalIndent}} + */ getVideoPosition (width, height, containerWidth, containerHeight) { if (this.stream && this.isScreenSharing()) { return getDesktopVideoPosition( width, @@ -238,6 +264,11 @@ class VideoContainer extends LargeContainer { }); } + /** + * Update video stream. + * @param {JitsiTrack?} stream new stream + * @param {string} videoType video type + */ setStream (stream, videoType) { this.stream = stream; this.videoType = videoType; @@ -250,10 +281,18 @@ class VideoContainer extends LargeContainer { }); } + /** + * Check if current video stream is screen sharing. + * @returns {boolean} + */ isScreenSharing () { return this.videoType === 'desktop'; } + /** + * Show or hide user avatar. + * @param {boolean} show + */ showAvatar (show) { this.$avatar.css("visibility", show ? "visible" : "hidden"); } @@ -289,7 +328,9 @@ class VideoContainer extends LargeContainer { } } - +/** + * Manager for all Large containers. + */ export default class LargeVideoManager { constructor () { this.containers = {}; @@ -356,6 +397,13 @@ export default class LargeVideoManager { return this.videoContainer.id; } + /** + * Update large video. + * Switches to large video even if previously other container was visible. + * @param {JitsiTrack?} stream new stream + * @param {string?} videoType new video type + * @returns {Promise} + */ updateLargeVideo (smallVideo, videoType, largeVideoUpdatedCallBack) { let id = getStreamId(smallVideo.stream); @@ -380,16 +428,29 @@ export default class LargeVideoManager { }); } + /** + * Update container size optionally taking side bar size into account. + * @param {boolean} isSideBarVisible if side bar is visible. + */ updateContainerSize (isSideBarVisible) { this.width = UIUtil.getAvailableVideoWidth(isSideBarVisible); this.height = window.innerHeight; } + /** + * Resize Large container of specified type. + * @param {string} type type of container which should be resized. + * @param {boolean} [animate=false] if resize process should be animated. + */ resizeContainer (type, animate = false) { let container = this.getContainer(type); container.resize(this.width, this.height, animate); } + /** + * Resize all Large containers. + * @param {boolean} animate if resize process should be animated. + */ resize (animate) { // resize all containers Object.keys(this.containers) @@ -420,11 +481,20 @@ export default class LargeVideoManager { $("#dominantSpeakerAvatar").attr('src', avatarUrl); } + /** + * Show avatar on Large video container or not. + * @param {boolean} show + */ showAvatar (show) { show ? this.videoContainer.hide() : this.videoContainer.show(); this.videoContainer.showAvatar(show); } + /** + * Add container of specified type. + * @param {string} type container type + * @param {LargeContainer} container container to add. + */ addContainer (type, container) { if (this.containers[type]) { throw new Error(`container of type ${type} already exist`); @@ -434,6 +504,11 @@ export default class LargeVideoManager { this.resizeContainer(type); } + /** + * Get Large container of specified type. + * @param {string} type container type. + * @returns {LargeContainer} + */ getContainer (type) { let container = this.containers[type]; @@ -444,6 +519,10 @@ export default class LargeVideoManager { return container; } + /** + * Remove Large container of specified type. + * @param {string} type container type. + */ removeContainer (type) { if (!this.containers[type]) { throw new Error(`container of type ${type} doesn't exist`); @@ -452,6 +531,12 @@ export default class LargeVideoManager { delete this.containers[type]; } + /** + * Show Large container of specified type. + * Does nothing if such container is already visible. + * @param {string} type container type. + * @returns {Promise} + */ showContainer (type) { if (this.state === type) { return Promise.resolve(); diff --git a/modules/UI/videolayout/VideoLayout.js b/modules/UI/videolayout/VideoLayout.js index f460f2647..5d5b83651 100644 --- a/modules/UI/videolayout/VideoLayout.js +++ b/modules/UI/videolayout/VideoLayout.js @@ -997,7 +997,7 @@ var VideoLayout = { // update current small video and the old one smallVideo.updateView(); oldSmallVideo && oldSmallVideo.updateView(); - }); + }); } else if (currentId) { let currentSmallVideo = this.getSmallVideo(currentId);