您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387
  1. /* global APP, JitsiMeetJS, $, config, interfaceConfig, toastr */
  2. /* jshint -W101 */
  3. var UI = {};
  4. import Chat from "./side_pannels/chat/Chat";
  5. import Toolbar from "./toolbars/Toolbar";
  6. import ToolbarToggler from "./toolbars/ToolbarToggler";
  7. import BottomToolbar from "./toolbars/BottomToolbar";
  8. import ContactList from "./side_pannels/contactlist/ContactList";
  9. import Avatar from "./avatar/Avatar";
  10. import PanelToggler from "./side_pannels/SidePanelToggler";
  11. import UIUtil from "./util/UIUtil";
  12. import UIEvents from "../../service/UI/UIEvents";
  13. import CQEvents from '../../service/connectionquality/CQEvents';
  14. import EtherpadManager from './etherpad/Etherpad';
  15. import SharedVideoManager from './shared_video/SharedVideo';
  16. import Recording from "./recording/Recording";
  17. import VideoLayout from "./videolayout/VideoLayout";
  18. import FilmStrip from "./videolayout/FilmStrip";
  19. import SettingsMenu from "./side_pannels/settings/SettingsMenu";
  20. import Settings from "./../settings/Settings";
  21. import { reload } from '../util/helpers';
  22. import RingOverlay from "./ring_overlay/RingOverlay";
  23. var EventEmitter = require("events");
  24. UI.messageHandler = require("./util/MessageHandler");
  25. var messageHandler = UI.messageHandler;
  26. var JitsiPopover = require("./util/JitsiPopover");
  27. var Feedback = require("./Feedback");
  28. import FollowMe from "../FollowMe";
  29. var eventEmitter = new EventEmitter();
  30. UI.eventEmitter = eventEmitter;
  31. let etherpadManager;
  32. let sharedVideoManager;
  33. let followMeHandler;
  34. const TrackErrors = JitsiMeetJS.errors.track;
  35. const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
  36. microphone: {},
  37. camera: {}
  38. };
  39. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.UNSUPPORTED_RESOLUTION]
  40. = "dialog.cameraUnsupportedResolutionError";
  41. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.GENERAL]
  42. = "dialog.cameraUnknownError";
  43. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.PERMISSION_DENIED]
  44. = "dialog.cameraPermissionDeniedError";
  45. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.NOT_FOUND]
  46. = "dialog.cameraNotFoundError";
  47. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.CONSTRAINT_FAILED]
  48. = "dialog.cameraConstraintFailedError";
  49. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.GENERAL]
  50. = "dialog.micUnknownError";
  51. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.PERMISSION_DENIED]
  52. = "dialog.micPermissionDeniedError";
  53. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.NOT_FOUND]
  54. = "dialog.micNotFoundError";
  55. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.CONSTRAINT_FAILED]
  56. = "dialog.micConstraintFailedError";
  57. /**
  58. * Prompt user for nickname.
  59. */
  60. function promptDisplayName() {
  61. let nickRequiredMsg
  62. = APP.translation.translateString("dialog.displayNameRequired");
  63. let defaultNickMsg = APP.translation.translateString("defaultNickname");
  64. let message = `
  65. <h2 data-i18n="dialog.displayNameRequired">${nickRequiredMsg}</h2>
  66. <input name="displayName" type="text"
  67. data-i18n="[placeholder]defaultNickname"
  68. placeholder="${defaultNickMsg}" autofocus>`;
  69. // Don't use a translation string, because we're too early in the call and
  70. // the translation may not be initialised.
  71. let buttons = {Ok:true};
  72. let dialog = messageHandler.openDialog(
  73. null,
  74. message,
  75. true,
  76. buttons,
  77. function (e, v, m, f) {
  78. e.preventDefault();
  79. if (v) {
  80. let displayName = f.displayName;
  81. if (displayName) {
  82. UI.inputDisplayNameHandler(displayName);
  83. dialog.close();
  84. return;
  85. }
  86. }
  87. },
  88. function () {
  89. let form = $.prompt.getPrompt();
  90. let input = form.find("input[name='displayName']");
  91. input.focus();
  92. let button = form.find("button");
  93. button.attr("disabled", "disabled");
  94. input.keyup(function () {
  95. if (input.val()) {
  96. button.removeAttr("disabled");
  97. } else {
  98. button.attr("disabled", "disabled");
  99. }
  100. });
  101. }
  102. );
  103. }
  104. /**
  105. * Initialize chat.
  106. */
  107. function setupChat() {
  108. Chat.init(eventEmitter);
  109. $("#toggle_smileys").click(function() {
  110. Chat.toggleSmileys();
  111. });
  112. }
  113. /**
  114. * Initialize toolbars.
  115. */
  116. function setupToolbars() {
  117. Toolbar.init(eventEmitter);
  118. BottomToolbar.setupListeners(eventEmitter);
  119. }
  120. /**
  121. * Toggles the application in and out of full screen mode
  122. * (a.k.a. presentation mode in Chrome).
  123. * @see https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API
  124. */
  125. function toggleFullScreen () {
  126. // alternative standard method
  127. let isNotFullScreen = !document.fullscreenElement &&
  128. !document.mozFullScreenElement && // current working methods
  129. !document.webkitFullscreenElement &&
  130. !document.msFullscreenElement;
  131. if (isNotFullScreen) {
  132. if (document.documentElement.requestFullscreen) {
  133. document.documentElement.requestFullscreen();
  134. } else if (document.documentElement.msRequestFullscreen) {
  135. document.documentElement.msRequestFullscreen();
  136. } else if (document.documentElement.mozRequestFullScreen) {
  137. document.documentElement.mozRequestFullScreen();
  138. } else if (document.documentElement.webkitRequestFullscreen) {
  139. document.documentElement
  140. .webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
  141. }
  142. } else {
  143. if (document.exitFullscreen) {
  144. document.exitFullscreen();
  145. } else if (document.msExitFullscreen) {
  146. document.msExitFullscreen();
  147. } else if (document.mozCancelFullScreen) {
  148. document.mozCancelFullScreen();
  149. } else if (document.webkitExitFullscreen) {
  150. document.webkitExitFullscreen();
  151. }
  152. }
  153. }
  154. /**
  155. * Notify user that server has shut down.
  156. */
  157. UI.notifyGracefulShutdown = function () {
  158. messageHandler.openMessageDialog(
  159. 'dialog.serviceUnavailable',
  160. 'dialog.gracefulShutdown'
  161. );
  162. };
  163. /**
  164. * Notify user that reservation error happened.
  165. */
  166. UI.notifyReservationError = function (code, msg) {
  167. var title = APP.translation.generateTranslationHTML(
  168. "dialog.reservationError");
  169. var message = APP.translation.generateTranslationHTML(
  170. "dialog.reservationErrorMsg", {code: code, msg: msg});
  171. messageHandler.openDialog(
  172. title,
  173. message,
  174. true, {},
  175. function (event, value, message, formVals) {
  176. return false;
  177. }
  178. );
  179. };
  180. /**
  181. * Notify user that he has been kicked from the server.
  182. */
  183. UI.notifyKicked = function () {
  184. messageHandler.openMessageDialog("dialog.sessTerminated", "dialog.kickMessage");
  185. };
  186. /**
  187. * Notify user that conference was destroyed.
  188. * @param reason {string} the reason text
  189. */
  190. UI.notifyConferenceDestroyed = function (reason) {
  191. //FIXME: use Session Terminated from translation, but
  192. // 'reason' text comes from XMPP packet and is not translated
  193. var title = APP.translation.generateTranslationHTML("dialog.sessTerminated");
  194. messageHandler.openDialog(
  195. title, reason, true, {},
  196. function (event, value, message, formVals) {
  197. return false;
  198. }
  199. );
  200. };
  201. /**
  202. * Notify user that Jitsi Videobridge is not accessible.
  203. */
  204. UI.notifyBridgeDown = function () {
  205. messageHandler.showError("dialog.error", "dialog.bridgeUnavailable");
  206. };
  207. /**
  208. * Show chat error.
  209. * @param err the Error
  210. * @param msg
  211. */
  212. UI.showChatError = function (err, msg) {
  213. if (interfaceConfig.filmStripOnly) {
  214. return;
  215. }
  216. Chat.chatAddError(err, msg);
  217. };
  218. /**
  219. * Change nickname for the user.
  220. * @param {string} id user id
  221. * @param {string} displayName new nickname
  222. */
  223. UI.changeDisplayName = function (id, displayName) {
  224. ContactList.onDisplayNameChange(id, displayName);
  225. VideoLayout.onDisplayNameChanged(id, displayName);
  226. if (APP.conference.isLocalId(id) || id === 'localVideoContainer') {
  227. SettingsMenu.changeDisplayName(displayName);
  228. Chat.setChatConversationMode(!!displayName);
  229. }
  230. };
  231. /**
  232. * Intitialize conference UI.
  233. */
  234. UI.initConference = function () {
  235. let id = APP.conference.localId;
  236. Toolbar.updateRoomUrl(window.location.href);
  237. // Add myself to the contact list.
  238. ContactList.addContact(id);
  239. //update default button states before showing the toolbar
  240. //if local role changes buttons state will be again updated
  241. UI.updateLocalRole(false);
  242. // Once we've joined the muc show the toolbar
  243. ToolbarToggler.showToolbar();
  244. let displayName = config.displayJids ? id : Settings.getDisplayName();
  245. if (displayName) {
  246. UI.changeDisplayName('localVideoContainer', displayName);
  247. }
  248. // Make sure we configure our avatar id, before creating avatar for us
  249. UI.setUserEmail(id, Settings.getEmail());
  250. Toolbar.checkAutoEnableDesktopSharing();
  251. if(!interfaceConfig.filmStripOnly) {
  252. Feedback.init(eventEmitter);
  253. }
  254. // FollowMe attempts to copy certain aspects of the moderator's UI into the
  255. // other participants' UI. Consequently, it needs (1) read and write access
  256. // to the UI (depending on the moderator role of the local participant) and
  257. // (2) APP.conference as means of communication between the participants.
  258. followMeHandler = new FollowMe(APP.conference, UI);
  259. };
  260. UI.mucJoined = function () {
  261. VideoLayout.mucJoined();
  262. };
  263. /**
  264. * Setup some UI event listeners.
  265. */
  266. function registerListeners() {
  267. UI.addListener(UIEvents.ETHERPAD_CLICKED, function () {
  268. if (etherpadManager) {
  269. etherpadManager.toggleEtherpad();
  270. }
  271. });
  272. UI.addListener(UIEvents.SHARED_VIDEO_CLICKED, function () {
  273. if (sharedVideoManager) {
  274. sharedVideoManager.toggleSharedVideo();
  275. }
  276. });
  277. UI.addListener(UIEvents.FULLSCREEN_TOGGLE, toggleFullScreen);
  278. UI.addListener(UIEvents.TOGGLE_CHAT, UI.toggleChat);
  279. UI.addListener(UIEvents.TOGGLE_SETTINGS, function () {
  280. PanelToggler.toggleSettingsMenu();
  281. });
  282. UI.addListener(UIEvents.TOGGLE_CONTACT_LIST, UI.toggleContactList);
  283. UI.addListener(UIEvents.TOGGLE_FILM_STRIP, function () {
  284. UI.toggleFilmStrip();
  285. VideoLayout.resizeVideoArea(PanelToggler.isVisible(), true, false);
  286. });
  287. UI.addListener(UIEvents.FOLLOW_ME_ENABLED, function (isEnabled) {
  288. if (followMeHandler)
  289. followMeHandler.enableFollowMe(isEnabled);
  290. });
  291. }
  292. /**
  293. * Setup some DOM event listeners.
  294. */
  295. function bindEvents() {
  296. function onResize() {
  297. PanelToggler.resizeChat();
  298. VideoLayout.resizeVideoArea(PanelToggler.isVisible());
  299. }
  300. // Resize and reposition videos in full screen mode.
  301. $(document).on(
  302. 'webkitfullscreenchange mozfullscreenchange fullscreenchange',
  303. onResize
  304. );
  305. $(window).resize(onResize);
  306. }
  307. /**
  308. * Returns the shared document manager object.
  309. * @return {EtherpadManager} the shared document manager object
  310. */
  311. UI.getSharedVideoManager = function () {
  312. return sharedVideoManager;
  313. };
  314. /**
  315. * Starts the UI module and initializes all related components.
  316. *
  317. * @returns {boolean} true if the UI is ready and the conference should be
  318. * esablished, false - otherwise (for example in the case of welcome page)
  319. */
  320. UI.start = function () {
  321. document.title = interfaceConfig.APP_NAME;
  322. var setupWelcomePage = null;
  323. if(config.enableWelcomePage && window.location.pathname == "/" &&
  324. Settings.isWelcomePageEnabled()) {
  325. $("#videoconference_page").hide();
  326. if (!setupWelcomePage)
  327. setupWelcomePage = require("./welcome_page/WelcomePage");
  328. setupWelcomePage();
  329. // Return false to indicate that the UI hasn't been fully started and
  330. // conference ready. We're still waiting for input from the user.
  331. return false;
  332. }
  333. $("#welcome_page").hide();
  334. // Set the defaults for prompt dialogs.
  335. $.prompt.setDefaults({persistent: false});
  336. registerListeners();
  337. BottomToolbar.init();
  338. FilmStrip.init(eventEmitter);
  339. VideoLayout.init(eventEmitter);
  340. if (!interfaceConfig.filmStripOnly) {
  341. VideoLayout.initLargeVideo(PanelToggler.isVisible());
  342. }
  343. VideoLayout.resizeVideoArea(PanelToggler.isVisible(), true, true);
  344. ContactList.init(eventEmitter);
  345. bindEvents();
  346. sharedVideoManager = new SharedVideoManager(eventEmitter);
  347. if (!interfaceConfig.filmStripOnly) {
  348. $("#videospace").mousemove(function () {
  349. return ToolbarToggler.showToolbar();
  350. });
  351. setupToolbars();
  352. setupChat();
  353. // Initialise the recording module.
  354. if (config.enableRecording)
  355. Recording.init(eventEmitter, config.recordingType);
  356. // Display notice message at the top of the toolbar
  357. if (config.noticeMessage) {
  358. $('#noticeText').text(config.noticeMessage);
  359. $('#notice').css({display: 'block'});
  360. }
  361. $("#downloadlog").click(function (event) {
  362. let logs = APP.conference.getLogs();
  363. let data = encodeURIComponent(JSON.stringify(logs, null, ' '));
  364. let elem = event.target.parentNode;
  365. elem.download = 'meetlog.json';
  366. elem.href = 'data:application/json;charset=utf-8,\n' + data;
  367. });
  368. } else {
  369. $("#header").css("display", "none");
  370. $("#downloadlog").css("display", "none");
  371. BottomToolbar.hide();
  372. FilmStrip.setupFilmStripOnly();
  373. messageHandler.enableNotifications(false);
  374. $('body').popover("disable");
  375. JitsiPopover.enabled = false;
  376. }
  377. document.title = interfaceConfig.APP_NAME;
  378. if(config.requireDisplayName) {
  379. if (!APP.settings.getDisplayName()) {
  380. promptDisplayName();
  381. }
  382. }
  383. if (!interfaceConfig.filmStripOnly) {
  384. toastr.options = {
  385. "closeButton": true,
  386. "debug": false,
  387. "positionClass": "notification-bottom-right",
  388. "onclick": null,
  389. "showDuration": "300",
  390. "hideDuration": "1000",
  391. "timeOut": "2000",
  392. "extendedTimeOut": "1000",
  393. "showEasing": "swing",
  394. "hideEasing": "linear",
  395. "showMethod": "fadeIn",
  396. "hideMethod": "fadeOut",
  397. "reposition": function () {
  398. if (PanelToggler.isVisible()) {
  399. $("#toast-container").addClass("notification-bottom-right-center");
  400. } else {
  401. $("#toast-container").removeClass("notification-bottom-right-center");
  402. }
  403. },
  404. "newestOnTop": false
  405. };
  406. SettingsMenu.init(eventEmitter);
  407. }
  408. if(APP.tokenData.callee) {
  409. UI.showRingOverLay();
  410. }
  411. // Return true to indicate that the UI has been fully started and
  412. // conference ready.
  413. return true;
  414. };
  415. /**
  416. * Show local stream on UI.
  417. * @param {JitsiTrack} track stream to show
  418. */
  419. UI.addLocalStream = function (track) {
  420. switch (track.getType()) {
  421. case 'audio':
  422. VideoLayout.changeLocalAudio(track);
  423. break;
  424. case 'video':
  425. VideoLayout.changeLocalVideo(track);
  426. break;
  427. default:
  428. console.error("Unknown stream type: " + track.getType());
  429. break;
  430. }
  431. };
  432. /**
  433. * Show remote stream on UI.
  434. * @param {JitsiTrack} track stream to show
  435. */
  436. UI.addRemoteStream = function (track) {
  437. VideoLayout.onRemoteStreamAdded(track);
  438. };
  439. /**
  440. * Removed remote stream from UI.
  441. * @param {JitsiTrack} track stream to remove
  442. */
  443. UI.removeRemoteStream = function (track) {
  444. VideoLayout.onRemoteStreamRemoved(track);
  445. };
  446. function chatAddError(errorMessage, originalText) {
  447. return Chat.chatAddError(errorMessage, originalText);
  448. }
  449. /**
  450. * Update chat subject.
  451. * @param {string} subject new chat subject
  452. */
  453. UI.setSubject = function (subject) {
  454. Chat.setSubject(subject);
  455. };
  456. /**
  457. * Setup and show Etherpad.
  458. * @param {string} name etherpad id
  459. */
  460. UI.initEtherpad = function (name) {
  461. if (etherpadManager || !config.etherpad_base || !name) {
  462. return;
  463. }
  464. console.log('Etherpad is enabled');
  465. etherpadManager
  466. = new EtherpadManager(config.etherpad_base, name, eventEmitter);
  467. Toolbar.showEtherpadButton();
  468. };
  469. /**
  470. * Returns the shared document manager object.
  471. * @return {EtherpadManager} the shared document manager object
  472. */
  473. UI.getSharedDocumentManager = function () {
  474. return etherpadManager;
  475. };
  476. /**
  477. * Show user on UI.
  478. * @param {string} id user id
  479. * @param {string} displayName user nickname
  480. */
  481. UI.addUser = function (id, displayName) {
  482. UI.hideRingOverLay();
  483. ContactList.addContact(id);
  484. messageHandler.notify(
  485. displayName,'notify.somebody', 'connected', 'notify.connected'
  486. );
  487. if (!config.startAudioMuted ||
  488. config.startAudioMuted > APP.conference.membersCount)
  489. UIUtil.playSoundNotification('userJoined');
  490. // Add Peer's container
  491. VideoLayout.addParticipantContainer(id);
  492. // Configure avatar
  493. UI.setUserEmail(id);
  494. // set initial display name
  495. if(displayName)
  496. UI.changeDisplayName(id, displayName);
  497. };
  498. /**
  499. * Remove user from UI.
  500. * @param {string} id user id
  501. * @param {string} displayName user nickname
  502. */
  503. UI.removeUser = function (id, displayName) {
  504. ContactList.removeContact(id);
  505. messageHandler.notify(
  506. displayName,'notify.somebody', 'disconnected', 'notify.disconnected'
  507. );
  508. if (!config.startAudioMuted
  509. || config.startAudioMuted > APP.conference.membersCount) {
  510. UIUtil.playSoundNotification('userLeft');
  511. }
  512. VideoLayout.removeParticipantContainer(id);
  513. };
  514. UI.updateUserStatus = function (id, status) {
  515. VideoLayout.setPresenceStatus(id, status);
  516. };
  517. /**
  518. * Update videotype for specified user.
  519. * @param {string} id user id
  520. * @param {string} newVideoType new videotype
  521. */
  522. UI.onPeerVideoTypeChanged = (id, newVideoType) => {
  523. VideoLayout.onVideoTypeChanged(id, newVideoType);
  524. };
  525. /**
  526. * Update local user role and show notification if user is moderator.
  527. * @param {boolean} isModerator if local user is moderator or not
  528. */
  529. UI.updateLocalRole = function (isModerator) {
  530. VideoLayout.showModeratorIndicator();
  531. Toolbar.showSipCallButton(isModerator);
  532. Toolbar.showSharedVideoButton(isModerator);
  533. Recording.showRecordingButton(isModerator);
  534. SettingsMenu.showStartMutedOptions(isModerator);
  535. SettingsMenu.showFollowMeOptions(isModerator);
  536. if (isModerator) {
  537. messageHandler.notify(null, "notify.me", 'connected', "notify.moderator");
  538. Recording.checkAutoRecord();
  539. }
  540. };
  541. /**
  542. * Check the role for the user and reflect it in the UI, moderator ui indication
  543. * and notifies user who is the moderator
  544. * @param user to check for moderator
  545. */
  546. UI.updateUserRole = function (user) {
  547. VideoLayout.showModeratorIndicator();
  548. if (!user.isModerator()) {
  549. return;
  550. }
  551. var displayName = user.getDisplayName();
  552. if (displayName) {
  553. messageHandler.notify(
  554. displayName, 'notify.somebody',
  555. 'connected', 'notify.grantedTo', {
  556. to: UIUtil.escapeHtml(displayName)
  557. }
  558. );
  559. } else {
  560. messageHandler.notify(
  561. '', 'notify.somebody',
  562. 'connected', 'notify.grantedToUnknown', {}
  563. );
  564. }
  565. };
  566. /**
  567. * Toggles smileys in the chat.
  568. */
  569. UI.toggleSmileys = function () {
  570. Chat.toggleSmileys();
  571. };
  572. /**
  573. * Toggles film strip.
  574. */
  575. UI.toggleFilmStrip = function () {
  576. var self = FilmStrip;
  577. self.toggleFilmStrip.apply(self, arguments);
  578. };
  579. /**
  580. * Indicates if the film strip is currently visible or not.
  581. * @returns {true} if the film strip is currently visible, otherwise
  582. */
  583. UI.isFilmStripVisible = function () {
  584. return FilmStrip.isFilmStripVisible();
  585. };
  586. /**
  587. * Toggles chat panel.
  588. */
  589. UI.toggleChat = function () {
  590. PanelToggler.toggleChat();
  591. };
  592. /**
  593. * Toggles contact list panel.
  594. */
  595. UI.toggleContactList = function () {
  596. PanelToggler.toggleContactList();
  597. };
  598. /**
  599. * Handle new user display name.
  600. */
  601. UI.inputDisplayNameHandler = function (newDisplayName) {
  602. eventEmitter.emit(UIEvents.NICKNAME_CHANGED, newDisplayName);
  603. };
  604. /**
  605. * Return the type of the remote video.
  606. * @param jid the jid for the remote video
  607. * @returns the video type video or screen.
  608. */
  609. UI.getRemoteVideoType = function (jid) {
  610. return VideoLayout.getRemoteVideoType(jid);
  611. };
  612. UI.connectionIndicatorShowMore = function(id) {
  613. VideoLayout.showMore(id);
  614. };
  615. // FIXME check if someone user this
  616. UI.showLoginPopup = function(callback) {
  617. console.log('password is required');
  618. var message = '<h2 data-i18n="dialog.passwordRequired">';
  619. message += APP.translation.translateString(
  620. "dialog.passwordRequired");
  621. message += '</h2>' +
  622. '<input name="username" type="text" ' +
  623. 'placeholder="user@domain.net" autofocus>' +
  624. '<input name="password" ' +
  625. 'type="password" data-i18n="[placeholder]dialog.userPassword"' +
  626. ' placeholder="user password">';
  627. messageHandler.openTwoButtonDialog(null, null, null, message,
  628. true,
  629. "dialog.Ok",
  630. function (e, v, m, f) {
  631. if (v) {
  632. if (f.username && f.password) {
  633. callback(f.username, f.password);
  634. }
  635. }
  636. },
  637. null, null, ':input:first'
  638. );
  639. };
  640. UI.askForNickname = function () {
  641. return window.prompt('Your nickname (optional)');
  642. };
  643. /**
  644. * Sets muted audio state for participant
  645. */
  646. UI.setAudioMuted = function (id, muted) {
  647. VideoLayout.onAudioMute(id, muted);
  648. if (APP.conference.isLocalId(id)) {
  649. Toolbar.markAudioIconAsMuted(muted);
  650. }
  651. };
  652. /**
  653. * Sets muted video state for participant
  654. */
  655. UI.setVideoMuted = function (id, muted) {
  656. VideoLayout.onVideoMute(id, muted);
  657. if (APP.conference.isLocalId(id)) {
  658. Toolbar.markVideoIconAsMuted(muted);
  659. }
  660. };
  661. /**
  662. * Adds a listener that would be notified on the given type of event.
  663. *
  664. * @param type the type of the event we're listening for
  665. * @param listener a function that would be called when notified
  666. */
  667. UI.addListener = function (type, listener) {
  668. eventEmitter.on(type, listener);
  669. };
  670. /**
  671. * Removes the given listener for the given type of event.
  672. *
  673. * @param type the type of the event we're listening for
  674. * @param listener the listener we want to remove
  675. */
  676. UI.removeListener = function (type, listener) {
  677. eventEmitter.removeListener(type, listener);
  678. };
  679. UI.clickOnVideo = function (videoNumber) {
  680. var remoteVideos = $(".videocontainer:not(#mixedstream)");
  681. if (remoteVideos.length > videoNumber) {
  682. remoteVideos[videoNumber].click();
  683. }
  684. };
  685. //Used by torture
  686. UI.showToolbar = function () {
  687. return ToolbarToggler.showToolbar();
  688. };
  689. //Used by torture
  690. UI.dockToolbar = function (isDock) {
  691. ToolbarToggler.dockToolbar(isDock);
  692. };
  693. /**
  694. * Updates the avatar for participant.
  695. * @param {string} id user id
  696. * @param {stirng} avatarUrl the URL for the avatar
  697. */
  698. function changeAvatar(id, avatarUrl) {
  699. VideoLayout.changeUserAvatar(id, avatarUrl);
  700. ContactList.changeUserAvatar(id, avatarUrl);
  701. if (APP.conference.isLocalId(id)) {
  702. SettingsMenu.changeAvatar(avatarUrl);
  703. }
  704. }
  705. /**
  706. * Update user email.
  707. * @param {string} id user id
  708. * @param {stirng} email user email
  709. */
  710. UI.setUserEmail = function (id, email) {
  711. // update avatar
  712. Avatar.setUserEmail(id, email);
  713. changeAvatar(id, Avatar.getAvatarUrl(id));
  714. };
  715. /**
  716. * Update user avatar URL.
  717. * @param {string} id user id
  718. * @param {stirng} url user avatar url
  719. */
  720. UI.setUserAvatarUrl = function (id, url) {
  721. // update avatar
  722. Avatar.setUserAvatarUrl(id, url);
  723. changeAvatar(id, Avatar.getAvatarUrl(id));
  724. };
  725. /**
  726. * Notify user that connection failed.
  727. * @param {string} stropheErrorMsg raw Strophe error message
  728. */
  729. UI.notifyConnectionFailed = function (stropheErrorMsg) {
  730. var title = APP.translation.generateTranslationHTML(
  731. "dialog.error");
  732. var message;
  733. if (stropheErrorMsg) {
  734. message = APP.translation.generateTranslationHTML(
  735. "dialog.connectErrorWithMsg", {msg: stropheErrorMsg});
  736. } else {
  737. message = APP.translation.generateTranslationHTML(
  738. "dialog.connectError");
  739. }
  740. messageHandler.openDialog(
  741. title, message, true, {}, function (e, v, m, f) { return false; }
  742. );
  743. };
  744. /**
  745. * Notify user that maximum users limit has been reached.
  746. */
  747. UI.notifyMaxUsersLimitReached = function () {
  748. var title = APP.translation.generateTranslationHTML(
  749. "dialog.error");
  750. var message = APP.translation.generateTranslationHTML(
  751. "dialog.maxUsersLimitReached");
  752. messageHandler.openDialog(
  753. title, message, true, {}, function (e, v, m, f) { return false; }
  754. );
  755. };
  756. /**
  757. * Notify user that he was automatically muted when joned the conference.
  758. */
  759. UI.notifyInitiallyMuted = function () {
  760. messageHandler.notify(
  761. null, "notify.mutedTitle", "connected", "notify.muted", null, {timeOut: 120000}
  762. );
  763. };
  764. /**
  765. * Mark user as dominant speaker.
  766. * @param {string} id user id
  767. */
  768. UI.markDominantSpeaker = function (id) {
  769. VideoLayout.onDominantSpeakerChanged(id);
  770. };
  771. UI.handleLastNEndpoints = function (ids, enteringIds) {
  772. VideoLayout.onLastNEndpointsChanged(ids, enteringIds);
  773. };
  774. /**
  775. * Update audio level visualization for specified user.
  776. * @param {string} id user id
  777. * @param {number} lvl audio level
  778. */
  779. UI.setAudioLevel = function (id, lvl) {
  780. VideoLayout.setAudioLevel(id, lvl);
  781. };
  782. /**
  783. * Update state of desktop sharing buttons.
  784. */
  785. UI.updateDesktopSharingButtons = function () {
  786. Toolbar.updateDesktopSharingButtonState();
  787. };
  788. /**
  789. * Hide connection quality statistics from UI.
  790. */
  791. UI.hideStats = function () {
  792. VideoLayout.hideStats();
  793. };
  794. /**
  795. * Update local connection quality statistics.
  796. * @param {number} percent
  797. * @param {object} stats
  798. */
  799. UI.updateLocalStats = function (percent, stats) {
  800. VideoLayout.updateLocalConnectionStats(percent, stats);
  801. };
  802. /**
  803. * Update connection quality statistics for remote user.
  804. * @param {string} id user id
  805. * @param {number} percent
  806. * @param {object} stats
  807. */
  808. UI.updateRemoteStats = function (id, percent, stats) {
  809. VideoLayout.updateConnectionStats(id, percent, stats);
  810. };
  811. /**
  812. * Mark video as interrupted or not.
  813. * @param {boolean} interrupted if video is interrupted
  814. */
  815. UI.markVideoInterrupted = function (interrupted) {
  816. if (interrupted) {
  817. VideoLayout.onVideoInterrupted();
  818. } else {
  819. VideoLayout.onVideoRestored();
  820. }
  821. };
  822. /**
  823. * Mark room as locked or not.
  824. * @param {boolean} locked if room is locked.
  825. */
  826. UI.markRoomLocked = function (locked) {
  827. if (locked) {
  828. Toolbar.lockLockButton();
  829. } else {
  830. Toolbar.unlockLockButton();
  831. }
  832. };
  833. /**
  834. * Add chat message.
  835. * @param {string} from user id
  836. * @param {string} displayName user nickname
  837. * @param {string} message message text
  838. * @param {number} stamp timestamp when message was created
  839. */
  840. UI.addMessage = function (from, displayName, message, stamp) {
  841. Chat.updateChatConversation(from, displayName, message, stamp);
  842. };
  843. UI.updateDTMFSupport = function (isDTMFSupported) {
  844. //TODO: enable when the UI is ready
  845. //Toolbar.showDialPadButton(dtmfSupport);
  846. };
  847. /**
  848. * Invite participants to conference.
  849. * @param {string} roomUrl
  850. * @param {string} conferenceName
  851. * @param {string} key
  852. * @param {string} nick
  853. */
  854. UI.inviteParticipants = function (roomUrl, conferenceName, key, nick) {
  855. let keyText = "";
  856. if (key) {
  857. keyText = APP.translation.translateString(
  858. "email.sharedKey", {sharedKey: key}
  859. );
  860. }
  861. let and = APP.translation.translateString("email.and");
  862. let supportedBrowsers = `Chromium, Google Chrome ${and} Opera`;
  863. let subject = APP.translation.translateString(
  864. "email.subject", {appName:interfaceConfig.APP_NAME, conferenceName}
  865. );
  866. let body = APP.translation.translateString(
  867. "email.body", {
  868. appName:interfaceConfig.APP_NAME,
  869. sharedKeyText: keyText,
  870. roomUrl,
  871. supportedBrowsers
  872. }
  873. );
  874. body = body.replace(/\n/g, "%0D%0A");
  875. if (nick) {
  876. body += "%0D%0A%0D%0A" + UIUtil.escapeHtml(nick);
  877. }
  878. if (interfaceConfig.INVITATION_POWERED_BY) {
  879. body += "%0D%0A%0D%0A--%0D%0Apowered by jitsi.org";
  880. }
  881. window.open(`mailto:?subject=${subject}&body=${body}`, '_blank');
  882. };
  883. /**
  884. * Show user feedback dialog if its required or just show "thank you" dialog.
  885. * @returns {Promise} when dialog is closed.
  886. */
  887. UI.requestFeedback = function () {
  888. return new Promise(function (resolve, reject) {
  889. if (Feedback.isEnabled()) {
  890. // If the user has already entered feedback, we'll show the window and
  891. // immidiately start the conference dispose timeout.
  892. if (Feedback.feedbackScore > 0) {
  893. Feedback.openFeedbackWindow();
  894. resolve();
  895. } else { // Otherwise we'll wait for user's feedback.
  896. Feedback.openFeedbackWindow(resolve);
  897. }
  898. } else {
  899. // If the feedback functionality isn't enabled we show a thank you
  900. // dialog.
  901. messageHandler.openMessageDialog(
  902. null, null, null,
  903. APP.translation.translateString(
  904. "dialog.thankYou", {appName:interfaceConfig.APP_NAME}
  905. )
  906. );
  907. resolve();
  908. }
  909. });
  910. };
  911. UI.updateRecordingState = function (state) {
  912. Recording.updateRecordingState(state);
  913. };
  914. UI.notifyTokenAuthFailed = function () {
  915. messageHandler.showError("dialog.error", "dialog.tokenAuthFailed");
  916. };
  917. UI.notifyInternalError = function () {
  918. messageHandler.showError("dialog.sorry", "dialog.internalError");
  919. };
  920. UI.notifyFocusDisconnected = function (focus, retrySec) {
  921. messageHandler.notify(
  922. null, "notify.focus",
  923. 'disconnected', "notify.focusFail",
  924. {component: focus, ms: retrySec}
  925. );
  926. };
  927. /**
  928. * Notify user that focus left the conference so page should be reloaded.
  929. */
  930. UI.notifyFocusLeft = function () {
  931. let title = APP.translation.generateTranslationHTML(
  932. 'dialog.serviceUnavailable'
  933. );
  934. let msg = APP.translation.generateTranslationHTML(
  935. 'dialog.jicofoUnavailable'
  936. );
  937. messageHandler.openDialog(
  938. title,
  939. msg,
  940. true, // persistent
  941. [{title: 'retry'}],
  942. function () {
  943. reload();
  944. return false;
  945. }
  946. );
  947. };
  948. /**
  949. * Updates auth info on the UI.
  950. * @param {boolean} isAuthEnabled if authentication is enabled
  951. * @param {string} [login] current login
  952. */
  953. UI.updateAuthInfo = function (isAuthEnabled, login) {
  954. let loggedIn = !!login;
  955. Toolbar.showAuthenticateButton(isAuthEnabled);
  956. if (isAuthEnabled) {
  957. Toolbar.setAuthenticatedIdentity(login);
  958. Toolbar.showLoginButton(!loggedIn);
  959. Toolbar.showLogoutButton(loggedIn);
  960. }
  961. };
  962. UI.onStartMutedChanged = function (startAudioMuted, startVideoMuted) {
  963. SettingsMenu.updateStartMutedBox(startAudioMuted, startVideoMuted);
  964. };
  965. /**
  966. * Update list of available physical devices.
  967. * @param {object[]} devices new list of available devices
  968. */
  969. UI.onAvailableDevicesChanged = function (devices) {
  970. SettingsMenu.changeDevicesList(devices);
  971. };
  972. /**
  973. * Sets microphone's <select> element to select microphone ID from settings.
  974. */
  975. UI.setSelectedMicFromSettings = function () {
  976. SettingsMenu.setSelectedMicFromSettings();
  977. };
  978. /**
  979. * Sets camera's <select> element to select camera ID from settings.
  980. */
  981. UI.setSelectedCameraFromSettings = function () {
  982. SettingsMenu.setSelectedCameraFromSettings();
  983. };
  984. /**
  985. * Sets audio outputs's <select> element to select audio output ID from
  986. * settings.
  987. */
  988. UI.setSelectedAudioOutputFromSettings = function () {
  989. SettingsMenu.setSelectedAudioOutputFromSettings();
  990. };
  991. /**
  992. * Returns the id of the current video shown on large.
  993. * Currently used by tests (torture).
  994. */
  995. UI.getLargeVideoID = function () {
  996. return VideoLayout.getLargeVideoID();
  997. };
  998. /**
  999. * Returns the current video shown on large.
  1000. * Currently used by tests (torture).
  1001. */
  1002. UI.getLargeVideo = function () {
  1003. return VideoLayout.getLargeVideo();
  1004. };
  1005. /**
  1006. * Shows dialog with a link to FF extension.
  1007. */
  1008. UI.showExtensionRequiredDialog = function (url) {
  1009. messageHandler.openMessageDialog(
  1010. "dialog.extensionRequired",
  1011. null,
  1012. null,
  1013. APP.translation.generateTranslationHTML(
  1014. "dialog.firefoxExtensionPrompt", {url: url}));
  1015. };
  1016. /**
  1017. * Shows dialog with combined information about camera and microphone errors.
  1018. * @param {JitsiTrackError} micError
  1019. * @param {JitsiTrackError} cameraError
  1020. */
  1021. UI.showDeviceErrorDialog = function (micError, cameraError) {
  1022. let localStoragePropName = "doNotShowErrorAgain";
  1023. let isMicJitsiTrackErrorAndHasName = micError && micError.name &&
  1024. micError instanceof JitsiMeetJS.JitsiTrackError;
  1025. let isCameraJitsiTrackErrorAndHasName = cameraError && cameraError.name &&
  1026. cameraError instanceof JitsiMeetJS.JitsiTrackError;
  1027. let showDoNotShowWarning = false;
  1028. if (micError && cameraError && isMicJitsiTrackErrorAndHasName &&
  1029. isCameraJitsiTrackErrorAndHasName) {
  1030. showDoNotShowWarning = true;
  1031. } else if (micError && isMicJitsiTrackErrorAndHasName && !cameraError) {
  1032. showDoNotShowWarning = true;
  1033. } else if (cameraError && isCameraJitsiTrackErrorAndHasName && !micError) {
  1034. showDoNotShowWarning = true;
  1035. }
  1036. if (micError) {
  1037. localStoragePropName += "-mic-" + micError.name;
  1038. }
  1039. if (cameraError) {
  1040. localStoragePropName += "-camera-" + cameraError.name;
  1041. }
  1042. if (showDoNotShowWarning) {
  1043. if (window.localStorage[localStoragePropName] === "true") {
  1044. return;
  1045. }
  1046. }
  1047. let title = getTitleKey();
  1048. let titleMsg = `<span data-i18n="${title}"></span>`;
  1049. let cameraJitsiTrackErrorMsg = cameraError
  1050. ? JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[cameraError.name]
  1051. : undefined;
  1052. let micJitsiTrackErrorMsg = micError
  1053. ? JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[micError.name]
  1054. : undefined;
  1055. let cameraErrorMsg = cameraError
  1056. ? cameraJitsiTrackErrorMsg ||
  1057. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.GENERAL]
  1058. : "";
  1059. let micErrorMsg = micError
  1060. ? micJitsiTrackErrorMsg ||
  1061. JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.GENERAL]
  1062. : "";
  1063. let additionalCameraErrorMsg = !cameraJitsiTrackErrorMsg && cameraError &&
  1064. cameraError.message
  1065. ? `<div>${cameraError.message}</div>`
  1066. : ``;
  1067. let additionalMicErrorMsg = !micJitsiTrackErrorMsg && micError &&
  1068. micError.message
  1069. ? `<div>${micError.message}</div>`
  1070. : ``;
  1071. let doNotShowWarningAgainSection = showDoNotShowWarning
  1072. ? `<label>
  1073. <input type='checkbox' id='doNotShowWarningAgain'>
  1074. <span data-i18n='dialog.doNotShowWarningAgain'></span>
  1075. </label>`
  1076. : ``;
  1077. let message = '';
  1078. if (micError) {
  1079. message = `
  1080. ${message}
  1081. <h3 data-i18n='dialog.micErrorPresent'></h3>
  1082. <h4 data-i18n='${micErrorMsg}'></h4>
  1083. ${additionalMicErrorMsg}`;
  1084. }
  1085. if (cameraError) {
  1086. message = `
  1087. ${message}
  1088. <h3 data-i18n='dialog.cameraErrorPresent'></h3>
  1089. <h4 data-i18n='${cameraErrorMsg}'></h4>
  1090. ${additionalCameraErrorMsg}`;
  1091. }
  1092. message = `${message}${doNotShowWarningAgainSection}`;
  1093. messageHandler.openDialog(
  1094. titleMsg,
  1095. message,
  1096. false,
  1097. {Ok: true},
  1098. function () {
  1099. let form = $.prompt.getPrompt();
  1100. if (form) {
  1101. let input = form.find("#doNotShowWarningAgain");
  1102. if (input.length) {
  1103. window.localStorage[localStoragePropName] =
  1104. input.prop("checked");
  1105. }
  1106. }
  1107. }
  1108. );
  1109. APP.translation.translateElement($(".jqibox"));
  1110. function getTitleKey() {
  1111. let title = "dialog.error";
  1112. if (micError && micError.name === TrackErrors.PERMISSION_DENIED) {
  1113. if (cameraError && cameraError.name === TrackErrors.PERMISSION_DENIED) {
  1114. title = "dialog.permissionDenied";
  1115. } else if (!cameraError) {
  1116. title = "dialog.permissionDenied";
  1117. }
  1118. } else if (cameraError &&
  1119. cameraError.name === TrackErrors.PERMISSION_DENIED) {
  1120. title = "dialog.permissionDenied";
  1121. }
  1122. return title;
  1123. }
  1124. };
  1125. UI.updateDevicesAvailability = function (id, devices) {
  1126. VideoLayout.setDeviceAvailabilityIcons(id, devices);
  1127. };
  1128. /**
  1129. * Show shared video.
  1130. * @param {string} id the id of the sender of the command
  1131. * @param {string} url video url
  1132. * @param {string} attributes
  1133. */
  1134. UI.onSharedVideoStart = function (id, url, attributes) {
  1135. if (sharedVideoManager)
  1136. sharedVideoManager.onSharedVideoStart(id, url, attributes);
  1137. };
  1138. /**
  1139. * Update shared video.
  1140. * @param {string} id the id of the sender of the command
  1141. * @param {string} url video url
  1142. * @param {string} attributes
  1143. */
  1144. UI.onSharedVideoUpdate = function (id, url, attributes) {
  1145. if (sharedVideoManager)
  1146. sharedVideoManager.onSharedVideoUpdate(id, url, attributes);
  1147. };
  1148. /**
  1149. * Stop showing shared video.
  1150. * @param {string} id the id of the sender of the command
  1151. * @param {string} attributes
  1152. */
  1153. UI.onSharedVideoStop = function (id, attributes) {
  1154. if (sharedVideoManager)
  1155. sharedVideoManager.onSharedVideoStop(id, attributes);
  1156. };
  1157. /**
  1158. * Disables camera toolbar button.
  1159. */
  1160. UI.disableCameraButton = function () {
  1161. Toolbar.markVideoIconAsDisabled(true);
  1162. };
  1163. /**
  1164. * Enables camera toolbar button.
  1165. */
  1166. UI.enableCameraButton = function () {
  1167. Toolbar.markVideoIconAsDisabled(false);
  1168. };
  1169. /**
  1170. * Disables microphone toolbar button.
  1171. */
  1172. UI.disableMicrophoneButton = function () {
  1173. Toolbar.markAudioIconAsDisabled(true);
  1174. };
  1175. /**
  1176. * Enables microphone toolbar button.
  1177. */
  1178. UI.enableMicrophoneButton = function () {
  1179. Toolbar.markAudioIconAsDisabled(false);
  1180. };
  1181. let bottomToolbarEnabled = null;
  1182. UI.showRingOverLay = function () {
  1183. RingOverlay.show(APP.tokenData.callee);
  1184. ToolbarToggler.setAlwaysVisibleToolbar(true);
  1185. FilmStrip.toggleFilmStrip(false);
  1186. };
  1187. UI.hideRingOverLay = function () {
  1188. if(!RingOverlay.hide())
  1189. return;
  1190. ToolbarToggler.resetAlwaysVisibleToolbar();
  1191. FilmStrip.toggleFilmStrip(true);
  1192. };
  1193. module.exports = UI;