You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SettingsMenu.js 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. /* global $, APP, AJS, interfaceConfig, JitsiMeetJS */
  2. import { LANGUAGES } from "../../../../react/features/base/i18n";
  3. import UIUtil from "../../util/UIUtil";
  4. import UIEvents from "../../../../service/UI/UIEvents";
  5. import Settings from '../../../settings/Settings';
  6. const sidePanelsContainerId = 'sideToolbarContainer';
  7. const htmlStr = `
  8. <div id="settings_container" class="sideToolbarContainer__inner">
  9. <div class="title" data-i18n="settings.title"></div>
  10. <form class="aui">
  11. <div id="languagesSelectWrapper"
  12. class="sideToolbarBlock first hide">
  13. <select id="languagesSelect"></select>
  14. </div>
  15. <div id="deviceOptionsWrapper" class="hide">
  16. <div id="deviceOptionsTitle" class="subTitle hide"
  17. data-i18n="settings.audioVideo"></div>
  18. <div class="sideToolbarBlock first">
  19. <label class="first" data-i18n="settings.selectCamera">
  20. </label>
  21. <select id="selectCamera"></select>
  22. </div>
  23. <div class="sideToolbarBlock">
  24. <label data-i18n="settings.selectMic"></label>
  25. <select id="selectMic"></select>
  26. </div>
  27. <div class="sideToolbarBlock">
  28. <label data-i18n="settings.selectAudioOutput"></label>
  29. <select id="selectAudioOutput"></select>
  30. </div>
  31. </div>
  32. <div id="moderatorOptionsWrapper" class="hide">
  33. <div id="moderatorOptionsTitle" class="subTitle hide"
  34. data-i18n="settings.moderator"></div>
  35. <div id="startMutedOptions" class="hide">
  36. <div class="sideToolbarBlock first">
  37. <input type="checkbox" id="startAudioMuted">
  38. <label class="startMutedLabel" for="startAudioMuted"
  39. data-i18n="settings.startAudioMuted"></label>
  40. </div>
  41. <div class="sideToolbarBlock">
  42. <input type="checkbox" id="startVideoMuted">
  43. <label class="startMutedLabel" for="startVideoMuted"
  44. data-i18n="settings.startVideoMuted"></label>
  45. </div>
  46. </div>
  47. <div id="followMeOptions" class="hide">
  48. <div class="sideToolbarBlock">
  49. <input type="checkbox" id="followMeCheckBox">
  50. <label class="followMeLabel" for="followMeCheckBox"
  51. data-i18n="settings.followMe"></label>
  52. </div>
  53. </div>
  54. </div>
  55. </form>
  56. </div>`;
  57. function initHTML() {
  58. $(`#${sidePanelsContainerId}`)
  59. .append(htmlStr);
  60. // make sure we translate the panel, as adding it can be after i18n
  61. // library had initialized and translated already present html
  62. APP.translation.translateElement($(`#${sidePanelsContainerId}`));
  63. }
  64. /**
  65. * Generate html select options for available languages.
  66. *
  67. * @param {string[]} items available languages
  68. * @param {string} [currentLang] current language
  69. * @returns {string}
  70. */
  71. function generateLanguagesOptions(items, currentLang) {
  72. return items.map(function (lang) {
  73. let attrs = {
  74. value: lang,
  75. 'data-i18n': `languages:${lang}`
  76. };
  77. if (lang === currentLang) {
  78. attrs.selected = 'selected';
  79. }
  80. let attrsStr = UIUtil.attrsToString(attrs);
  81. return `<option ${attrsStr}></option>`;
  82. }).join('');
  83. }
  84. /**
  85. * Generate html select options for available physical devices.
  86. *
  87. * @param {{ deviceId, label }[]} items available devices
  88. * @param {string} [selectedId] id of selected device
  89. * @param {boolean} permissionGranted if permission to use selected device type
  90. * is granted
  91. * @returns {string}
  92. */
  93. function generateDevicesOptions(items, selectedId, permissionGranted) {
  94. if (!permissionGranted && items.length) {
  95. return '<option data-i18n="settings.noPermission"></option>';
  96. }
  97. var options = items.map(function (item) {
  98. let attrs = {
  99. value: item.deviceId
  100. };
  101. if (item.deviceId === selectedId) {
  102. attrs.selected = 'selected';
  103. }
  104. let attrsStr = UIUtil.attrsToString(attrs);
  105. return `<option ${attrsStr}>${item.label}</option>`;
  106. });
  107. if (!items.length) {
  108. options.unshift('<option data-i18n="settings.noDevice"></option>');
  109. }
  110. return options.join('');
  111. }
  112. /**
  113. * Replace html select element to select2 custom dropdown
  114. *
  115. * @param {jQueryElement} $el native select element
  116. * @param {function} onSelectedCb fired if item is selected
  117. */
  118. function initSelect2($el, onSelectedCb) {
  119. $el.auiSelect2({
  120. minimumResultsForSearch: Infinity
  121. });
  122. if (typeof onSelectedCb === 'function') {
  123. $el.change(onSelectedCb);
  124. }
  125. }
  126. export default {
  127. init (emitter) {
  128. initHTML();
  129. //LANGUAGES BOX
  130. if (UIUtil.isSettingEnabled('language')) {
  131. const wrapperId = 'languagesSelectWrapper';
  132. const selectId = 'languagesSelect';
  133. const selectEl = AJS.$(`#${selectId}`);
  134. let selectInput;
  135. selectEl.html(generateLanguagesOptions(
  136. LANGUAGES,
  137. APP.translation.getCurrentLanguage()
  138. ));
  139. initSelect2(selectEl, () => {
  140. const val = selectEl.val();
  141. selectInput[0].dataset.i18n = `languages:${val}`;
  142. APP.translation.translateElement(selectInput);
  143. emitter.emit(UIEvents.LANG_CHANGED, val);
  144. });
  145. //find new selectInput element
  146. selectInput = $(`#s2id_${selectId} .select2-chosen`);
  147. //first select fix for languages options
  148. selectInput[0].dataset.i18n =
  149. `languages:${APP.translation.getCurrentLanguage()}`;
  150. // translate selectInput, which is the currently selected language
  151. // otherwise there will be no selected option
  152. APP.translation.translateElement(selectInput);
  153. APP.translation.translateElement(selectEl);
  154. APP.translation.addLanguageChangedListener(
  155. lng => selectInput[0].dataset.i18n = `languages:${lng}`);
  156. UIUtil.setVisible(wrapperId, true);
  157. }
  158. // DEVICES LIST
  159. if (UIUtil.isSettingEnabled('devices')) {
  160. const wrapperId = 'deviceOptionsWrapper';
  161. JitsiMeetJS.mediaDevices.isDeviceListAvailable()
  162. .then((isDeviceListAvailable) => {
  163. if (isDeviceListAvailable &&
  164. JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
  165. this._initializeDeviceSelectionSettings(emitter);
  166. }
  167. });
  168. // Only show the subtitle if this isn't the only setting section.
  169. if (interfaceConfig.SETTINGS_SECTIONS.length > 1)
  170. UIUtil.setVisible("deviceOptionsTitle", true);
  171. UIUtil.setVisible(wrapperId, true);
  172. }
  173. // MODERATOR
  174. if (UIUtil.isSettingEnabled('moderator')) {
  175. const wrapperId = 'moderatorOptionsWrapper';
  176. // START MUTED
  177. $("#startMutedOptions").change(function () {
  178. let startAudioMuted = $("#startAudioMuted").is(":checked");
  179. let startVideoMuted = $("#startVideoMuted").is(":checked");
  180. emitter.emit(
  181. UIEvents.START_MUTED_CHANGED,
  182. startAudioMuted,
  183. startVideoMuted
  184. );
  185. });
  186. // FOLLOW ME
  187. const followMeToggle = document.getElementById('followMeCheckBox');
  188. followMeToggle.addEventListener('change', () => {
  189. const isFollowMeEnabled = followMeToggle.checked;
  190. emitter.emit(UIEvents.FOLLOW_ME_ENABLED, isFollowMeEnabled);
  191. });
  192. UIUtil.setVisible(wrapperId, true);
  193. }
  194. },
  195. _initializeDeviceSelectionSettings(emitter) {
  196. this.changeDevicesList([]);
  197. $('#selectCamera').change(function () {
  198. let cameraDeviceId = $(this).val();
  199. if (cameraDeviceId !== Settings.getCameraDeviceId()) {
  200. emitter.emit(UIEvents.VIDEO_DEVICE_CHANGED, cameraDeviceId);
  201. }
  202. });
  203. $('#selectMic').change(function () {
  204. let micDeviceId = $(this).val();
  205. if (micDeviceId !== Settings.getMicDeviceId()) {
  206. emitter.emit(UIEvents.AUDIO_DEVICE_CHANGED, micDeviceId);
  207. }
  208. });
  209. $('#selectAudioOutput').change(function () {
  210. let audioOutputDeviceId = $(this).val();
  211. if (audioOutputDeviceId !== Settings.getAudioOutputDeviceId()) {
  212. emitter.emit(
  213. UIEvents.AUDIO_OUTPUT_DEVICE_CHANGED, audioOutputDeviceId);
  214. }
  215. });
  216. },
  217. /**
  218. * If start audio muted/start video muted options should be visible or not.
  219. * @param {boolean} show
  220. */
  221. showStartMutedOptions (show) {
  222. if (show && UIUtil.isSettingEnabled('moderator')) {
  223. // Only show the subtitle if this isn't the only setting section.
  224. if (!$("#moderatorOptionsTitle").is(":visible")
  225. && interfaceConfig.SETTINGS_SECTIONS.length > 1)
  226. UIUtil.setVisible("moderatorOptionsTitle", true);
  227. UIUtil.setVisible("startMutedOptions", true);
  228. } else {
  229. // Only show the subtitle if this isn't the only setting section.
  230. if ($("#moderatorOptionsTitle").is(":visible"))
  231. UIUtil.setVisible("moderatorOptionsTitle", false);
  232. UIUtil.setVisible("startMutedOptions", false);
  233. }
  234. },
  235. updateStartMutedBox (startAudioMuted, startVideoMuted) {
  236. $("#startAudioMuted").attr("checked", startAudioMuted);
  237. $("#startVideoMuted").attr("checked", startVideoMuted);
  238. },
  239. /**
  240. * Shows/hides the follow me options in the settings dialog.
  241. *
  242. * @param {boolean} show {true} to show those options, {false} to hide them
  243. */
  244. showFollowMeOptions (show) {
  245. UIUtil.setVisible(
  246. "followMeOptions",
  247. show && UIUtil.isSettingEnabled('moderator'));
  248. },
  249. /**
  250. * Check if settings menu is visible or not.
  251. * @returns {boolean}
  252. */
  253. isVisible () {
  254. return UIUtil.isVisible(document.getElementById("settings_container"));
  255. },
  256. /**
  257. * Sets microphone's <select> element to select microphone ID from settings.
  258. */
  259. setSelectedMicFromSettings () {
  260. $('#selectMic').val(Settings.getMicDeviceId());
  261. },
  262. /**
  263. * Sets camera's <select> element to select camera ID from settings.
  264. */
  265. setSelectedCameraFromSettings () {
  266. $('#selectCamera').val(Settings.getCameraDeviceId());
  267. },
  268. /**
  269. * Sets audio outputs's <select> element to select audio output ID from
  270. * settings.
  271. */
  272. setSelectedAudioOutputFromSettings () {
  273. $('#selectAudioOutput').val(Settings.getAudioOutputDeviceId());
  274. },
  275. /**
  276. * Change available cameras/microphones or hide selects completely if
  277. * no devices available.
  278. * @param {{ deviceId, label, kind }[]} devices list of available devices
  279. */
  280. changeDevicesList (devices) {
  281. let $selectCamera= AJS.$('#selectCamera'),
  282. $selectMic = AJS.$('#selectMic'),
  283. $selectAudioOutput = AJS.$('#selectAudioOutput'),
  284. $selectAudioOutputParent = $selectAudioOutput.parent();
  285. let audio = devices.filter(device => device.kind === 'audioinput'),
  286. video = devices.filter(device => device.kind === 'videoinput'),
  287. audioOutput = devices
  288. .filter(device => device.kind === 'audiooutput'),
  289. selectedAudioDevice = audio.find(
  290. d => d.deviceId === Settings.getMicDeviceId()) || audio[0],
  291. selectedVideoDevice = video.find(
  292. d => d.deviceId === Settings.getCameraDeviceId()) || video[0],
  293. selectedAudioOutputDevice = audioOutput.find(
  294. d => d.deviceId === Settings.getAudioOutputDeviceId()),
  295. videoPermissionGranted =
  296. JitsiMeetJS.mediaDevices.isDevicePermissionGranted('video'),
  297. audioPermissionGranted =
  298. JitsiMeetJS.mediaDevices.isDevicePermissionGranted('audio');
  299. $selectCamera
  300. .html(generateDevicesOptions(
  301. video,
  302. selectedVideoDevice ? selectedVideoDevice.deviceId : '',
  303. videoPermissionGranted))
  304. .prop('disabled', !video.length || !videoPermissionGranted);
  305. initSelect2($selectCamera);
  306. $selectMic
  307. .html(generateDevicesOptions(
  308. audio,
  309. selectedAudioDevice ? selectedAudioDevice.deviceId : '',
  310. audioPermissionGranted))
  311. .prop('disabled', !audio.length || !audioPermissionGranted);
  312. initSelect2($selectMic);
  313. if (JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output')) {
  314. $selectAudioOutput
  315. .html(generateDevicesOptions(
  316. audioOutput,
  317. selectedAudioOutputDevice
  318. ? selectedAudioOutputDevice.deviceId
  319. : 'default',
  320. videoPermissionGranted || audioPermissionGranted))
  321. .prop('disabled', !audioOutput.length ||
  322. (!videoPermissionGranted && !audioPermissionGranted));
  323. initSelect2($selectAudioOutput);
  324. $selectAudioOutputParent.show();
  325. } else {
  326. $selectAudioOutputParent.hide();
  327. }
  328. APP.translation.translateElement($('#settings_container option'));
  329. }
  330. };