Merge pull request #812 from jitsi/attach-shortcuts-to-features

Attach keyboard shortcuts to features
This commit is contained in:
hristoterezov 2016-08-29 16:05:12 -05:00 committed by GitHub
commit cac7ccf176
6 changed files with 260 additions and 160 deletions

View File

@ -16,4 +16,9 @@
#keyboard-shortcuts .item-action { #keyboard-shortcuts .item-action {
color: #209EFF; color: #209EFF;
font-size: 14pt; font-size: 14pt;
padding-right: 5px;
}
#keyboard-shortcuts-list {
list-style-type: none;
} }

View File

@ -279,67 +279,7 @@
<div id="keyboard-shortcuts" class="keyboard-shortcuts" style="display:none;"> <div id="keyboard-shortcuts" class="keyboard-shortcuts" style="display:none;">
<div class="header"><h3 data-i18n="keyboardShortcuts.keyboardShortcuts"></h3></div> <div class="header"><h3 data-i18n="keyboardShortcuts.keyboardShortcuts"></h3></div>
<div class="content"> <div class="content">
<ul class="item"> <ul id="keyboard-shortcuts-list" class="item">
<li>
<span class="item-action">
<kbd class="regular-key">M</kbd>
</span>
<span class="item-description" data-i18n="keyboardShortcuts.mute"></span>
</li>
<li>
<span class="item-action">
<kbd class="regular-key">V</kbd>
</span>
<span class="item-description" data-i18n="keyboardShortcuts.videoMute"></span>
</li>
<li>
<span class="item-action">
<kbd class="regular-key">C</kbd>
</span>
<span class="item-description" data-i18n="keyboardShortcuts.toggleChat"></span>
</li>
<li>
<span class="item-action">
<kbd class="regular-key">R</kbd>
</span>
<span class="item-description" data-i18n="keyboardShortcuts.raiseHand"></span>
</li>
<li>
<span class="item-action">
<kbd class="regular-key">T</kbd>
</span>
<span class="item-description" data-i18n="keyboardShortcuts.pushToTalk"></span>
</li>
<li>
<span class="item-action">
<kbd class="regular-key">D</kbd>
</span>
<span class="item-description" data-i18n="keyboardShortcuts.toggleScreensharing"></span>
</li>
<li class="item-details">
<span class="item-action">
<kbd class="regular-key">F</kbd>
</span>
<span class="item-description" data-i18n="keyboardShortcuts.toggleFilmstrip"></span>
</li>
<li>
<span class="item-action">
<kbd class="regular-key">?</kbd>
</span>
<span class="item-description" data-i18n="keyboardShortcuts.toggleShortcuts"></span>
</li>
<li>
<span class="item-action">
<kbd class="regular-key">0</kbd>
</span>
<span class="item-description" data-i18n="keyboardShortcuts.focusLocal"></span>
</li>
<li>
<span class="item-action">
<kbd class="regular-key">1-9</kbd>
</span>
<span class="item-description" data-i18n="keyboardShortcuts.focusRemote"></span>
</li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -3,9 +3,22 @@ import UIUtil from '../util/UIUtil';
import UIEvents from '../../../service/UI/UIEvents'; import UIEvents from '../../../service/UI/UIEvents';
const defaultBottomToolbarButtons = { const defaultBottomToolbarButtons = {
'chat': '#bottom_toolbar_chat', 'chat': {
'contacts': '#bottom_toolbar_contact_list', id: '#bottom_toolbar_chat'
'filmstrip': '#bottom_toolbar_film_strip' },
'contacts': {
id: '#bottom_toolbar_contact_list'
},
'filmstrip': {
id: '#bottom_toolbar_film_strip',
shortcut: "F",
shortcutAttr: "filmstripPopover",
shortcutFunc: function() {
JitsiMeetJS.analytics.sendEvent("shortcut.film.toggled");
APP.UI.toggleFilmStrip();
},
shortcutDescription: "keyboardShortcuts.toggleFilmstrip"
}
}; };
const BottomToolbar = { const BottomToolbar = {
@ -32,6 +45,7 @@ const BottomToolbar = {
isEnabled() { isEnabled() {
return this.enabled; return this.enabled;
}, },
setupListeners (emitter) { setupListeners (emitter) {
UIUtil.hideDisabledButtons(defaultBottomToolbarButtons); UIUtil.hideDisabledButtons(defaultBottomToolbarButtons);
@ -52,6 +66,22 @@ const BottomToolbar = {
} }
}; };
Object.keys(defaultBottomToolbarButtons).forEach(
id => {
if (UIUtil.isButtonEnabled(id)) {
var button = defaultBottomToolbarButtons[id];
if (button.shortcut)
APP.keyboardshortcut.registerShortcut(
button.shortcut,
button.shortcutAttr,
button.shortcutFunc,
button.shortcutDescription
);
}
}
);
Object.keys(buttonHandlers).forEach( Object.keys(buttonHandlers).forEach(
buttonId => $(`#${buttonId}`).click(buttonHandlers[buttonId]) buttonId => $(`#${buttonId}`).click(buttonHandlers[buttonId])
); );

View File

@ -110,7 +110,8 @@ const buttonHandlers = {
}, },
"toolbar_button_fullScreen": function() { "toolbar_button_fullScreen": function() {
JitsiMeetJS.analytics.sendEvent('toolbar.fullscreen.enabled'); JitsiMeetJS.analytics.sendEvent('toolbar.fullscreen.enabled');
UIUtil.buttonClick("#toolbar_button_fullScreen", "icon-full-screen icon-exit-full-screen"); UIUtil.buttonClick("#toolbar_button_fullScreen",
"icon-full-screen icon-exit-full-screen");
emitter.emit(UIEvents.FULLSCREEN_TOGGLE); emitter.emit(UIEvents.FULLSCREEN_TOGGLE);
}, },
"toolbar_button_sip": function () { "toolbar_button_sip": function () {
@ -152,16 +153,64 @@ const buttonHandlers = {
} }
}; };
const defaultToolbarButtons = { const defaultToolbarButtons = {
'microphone': '#toolbar_button_mute', 'microphone': {
'camera': '#toolbar_button_camera', id: '#toolbar_button_mute',
'desktop': '#toolbar_button_desktopsharing', shortcut: 'M',
'security': '#toolbar_button_security', shortcutAttr: 'mutePopover',
'invite': '#toolbar_button_link', shortcutFunc: function() {
'chat': '#toolbar_button_chat', JitsiMeetJS.analytics.sendEvent('shortcut.audiomute.toggled');
'etherpad': '#toolbar_button_etherpad', APP.conference.toggleAudioMuted();
'fullscreen': '#toolbar_button_fullScreen', },
'settings': '#toolbar_button_settings', shortcutDescription: "keyboardShortcuts.mute"
'hangup': '#toolbar_button_hangup' },
'camera': {
id: '#toolbar_button_camera',
shortcut: 'V',
shortcutAttr: 'toggleVideoPopover',
shortcutFunc: function() {
JitsiMeetJS.analytics.sendEvent('shortcut.videomute.toggled');
APP.conference.toggleVideoMuted();
},
shortcutDescription: "keyboardShortcuts.videoMute"
},
'desktop': {
id: '#toolbar_button_desktopsharing',
shortcut: 'D',
shortcutAttr: 'toggleDesktopSharingPopover',
shortcutFunc: function() {
JitsiMeetJS.analytics.sendEvent('shortcut.screen.toggled');
APP.conference.toggleScreenSharing();
},
shortcutDescription: "keyboardShortcuts.toggleScreensharing"
},
'security': {
id: '#toolbar_button_security'
},
'invite': {
id: '#toolbar_button_link'
},
'chat': {
id: '#toolbar_button_chat',
shortcut: 'C',
shortcutAttr: 'toggleChatPopover',
shortcutFunc: function() {
JitsiMeetJS.analytics.sendEvent('shortcut.chat.toggled');
APP.UI.toggleChat();
},
shortcutDescription: "keyboardShortcuts.toggleChat"
},
'etherpad': {
id: '#toolbar_button_etherpad'
},
'fullscreen': {
id: '#toolbar_button_fullScreen'
},
'settings': {
id: '#toolbar_button_settings'
},
'hangup': {
id: '#toolbar_button_hangup'
}
}; };
function dialpadButtonClicked() { function dialpadButtonClicked() {
@ -197,6 +246,22 @@ const Toolbar = {
UIUtil.hideDisabledButtons(defaultToolbarButtons); UIUtil.hideDisabledButtons(defaultToolbarButtons);
Object.keys(defaultToolbarButtons).forEach(
id => {
if (UIUtil.isButtonEnabled(id)) {
var button = defaultToolbarButtons[id];
if (button.shortcut)
APP.keyboardshortcut.registerShortcut(
button.shortcut,
button.shortcutAttr,
button.shortcutFunc,
button.shortcutDescription
);
}
}
);
Object.keys(buttonHandlers).forEach( Object.keys(buttonHandlers).forEach(
buttonId => $(`#${buttonId}`).click(function(event) { buttonId => $(`#${buttonId}`).click(function(event) {
!$(this).prop('disabled') && buttonHandlers[buttonId](event); !$(this).prop('disabled') && buttonHandlers[buttonId](event);

View File

@ -130,7 +130,7 @@
var selector = Object.keys(mappings) var selector = Object.keys(mappings)
.map(function (buttonName) { .map(function (buttonName) {
return UIUtil.isButtonEnabled(buttonName) return UIUtil.isButtonEnabled(buttonName)
? null : mappings[buttonName]; }) ? null : mappings[buttonName].id; })
.filter(function (item) { return item; }) .filter(function (item) { return item; })
.join(','); .join(',');
$(selector).hide(); $(selector).hide();

View File

@ -1,91 +1,62 @@
/* global APP, $, JitsiMeetJS */ /* global APP, $, JitsiMeetJS */
//maps keycode to character, id of popover for given function and function
var shortcuts = {}; /**
function initShortcutHandlers() { * Initialise global shortcuts.
shortcuts = { * Global shortcuts are shortcuts for features that don't have a button or
"ESCAPE": { * link associated with the action. In other words they represent actions
character: "Esc", * triggered _only_ with a shortcut.
function: function() { */
function initGlobalShortcuts() {
KeyboardShortcut.registerShortcut("ESCAPE", null, function() {
APP.UI.showKeyboardShortcutsPanel(false); APP.UI.showKeyboardShortcutsPanel(false);
} });
},
"C": {
character: "C",
id: "toggleChatPopover",
function: function() {
JitsiMeetJS.analytics.sendEvent('shortcut.chat.toggled');
APP.UI.toggleChat();
}
},
"D": {
character: "D",
id: "toggleDesktopSharingPopover",
function: function () {
JitsiMeetJS.analytics.sendEvent('shortcut.screen.toggled');
APP.conference.toggleScreenSharing();
}
},
"F": {
character: "F",
id: "filmstripPopover",
function: function() {
JitsiMeetJS.analytics.sendEvent('shortcut.film.toggled');
APP.UI.toggleFilmStrip();
}
},
"M": {
character: "M",
id: "mutePopover",
function: function() {
JitsiMeetJS.analytics.sendEvent('shortcut.audiomute.toggled');
APP.conference.toggleAudioMuted();
}
},
"R": {
character: "R",
function: function() {
JitsiMeetJS.analytics.sendEvent('shortcut.raisedhand.toggled');
APP.conference.maybeToggleRaisedHand();
}
}, KeyboardShortcut.registerShortcut("?", null, function() {
"T": { JitsiMeetJS.analytics.sendEvent("shortcut.shortcut.help");
character: "T",
function: function() {
JitsiMeetJS.analytics.sendEvent('shortcut.talk.clicked');
APP.conference.muteAudio(true);
}
},
"V": {
character: "V",
id: "toggleVideoPopover",
function: function() {
JitsiMeetJS.analytics.sendEvent('shortcut.videomute.toggled');
APP.conference.toggleVideoMuted();
}
},
"?": {
character: "?",
function: function(e) {
JitsiMeetJS.analytics.sendEvent('shortcut.shortcut.help');
APP.UI.toggleKeyboardShortcutsPanel(); APP.UI.toggleKeyboardShortcutsPanel();
} }, "keyboardShortcuts.toggleShortcuts");
}
}; KeyboardShortcut.registerShortcut("R", null, function() {
JitsiMeetJS.analytics.sendEvent("shortcut.raisedhand.toggled");
APP.conference.maybeToggleRaisedHand();
}, "keyboardShortcuts.raiseHand");
KeyboardShortcut.registerShortcut("T", null, function() {
JitsiMeetJS.analytics.sendEvent("shortcut.talk.clicked");
APP.conference.muteAudio(true);
}, "keyboardShortcuts.pushToTalk");
/**
* FIXME: Currently focus keys are directly implemented below in onkeyup.
* They should be moved to the SmallVideo instead.
*/
KeyboardShortcut._addShortcutToHelp("0", "keyboardShortcuts.focusLocal");
KeyboardShortcut._addShortcutToHelp("1-9", "keyboardShortcuts.focusRemote");
} }
/**
* Map of shortcuts. When a shortcut is registered it enters the mapping.
* @type {{}}
*/
let _shortcuts = {};
/**
* Maps keycode to character, id of popover for given function and function.
*/
var KeyboardShortcut = { var KeyboardShortcut = {
init: function () { init: function () {
initShortcutHandlers(); initGlobalShortcuts();
var self = this; var self = this;
window.onkeyup = function(e) { window.onkeyup = function(e) {
var key = self.getKeyboardKey(e).toUpperCase(); var key = self._getKeyboardKey(e).toUpperCase();
var num = parseInt(key, 10); var num = parseInt(key, 10);
if(!($(":focus").is("input[type=text]") || if(!($(":focus").is("input[type=text]") ||
$(":focus").is("input[type=password]") || $(":focus").is("input[type=password]") ||
$(":focus").is("textarea"))) { $(":focus").is("textarea"))) {
if (shortcuts.hasOwnProperty(key)) { if (_shortcuts.hasOwnProperty(key)) {
shortcuts[key].function(e); _shortcuts[key].function(e);
} }
else if (!isNaN(num) && num >= 0 && num <= 9) { else if (!isNaN(num) && num >= 0 && num <= 9) {
APP.UI.clickOnVideo(num + 1); APP.UI.clickOnVideo(num + 1);
@ -101,7 +72,7 @@ var KeyboardShortcut = {
if(!($(":focus").is("input[type=text]") || if(!($(":focus").is("input[type=text]") ||
$(":focus").is("input[type=password]") || $(":focus").is("input[type=password]") ||
$(":focus").is("textarea"))) { $(":focus").is("textarea"))) {
var key = self.getKeyboardKey(e).toUpperCase(); var key = self._getKeyboardKey(e).toUpperCase();
if(key === "T") { if(key === "T") {
if(APP.conference.isLocalAudioMuted()) if(APP.conference.isLocalAudioMuted())
APP.conference.muteAudio(false); APP.conference.muteAudio(false);
@ -112,20 +83,58 @@ var KeyboardShortcut = {
trigger: 'click hover', trigger: 'click hover',
content: function() { content: function() {
return this.getAttribute("content") + return this.getAttribute("content") +
self.getShortcut(this.getAttribute("shortcut")); self._getShortcut(this.getAttribute("shortcut"));
} }
}); });
}, },
/**
* Registers a new shortcut.
*
* @param shortcutChar the shortcut character triggering the action
* @param shortcutAttr the "shortcut" html element attribute mappring an
* element to this shortcut and used to show the shortcut character on the
* element tooltip
* @param exec the function to be executed when the shortcut is pressed
* @param helpDescription the description of the shortcut that would appear
* in the help menu
*/
registerShortcut: function( shortcutChar,
shortcutAttr,
exec,
helpDescription) {
_shortcuts[shortcutChar] = {
character: shortcutChar,
shortcutAttr: shortcutAttr,
function: exec
};
if (helpDescription)
this._addShortcutToHelp(shortcutChar, helpDescription);
},
/**
* Unregisters a shortcut.
*
* @param shortcutChar unregisters the given shortcut, which means it will
* no longer be usable
*/
unregisterShortcut: function(shortcutChar) {
_shortcuts.remove(shortcutChar);
this._removeShortcutFromHelp(shortcutChar);
},
/** /**
* *
* @param id indicates the popover associated with the shortcut * @param id indicates the popover associated with the shortcut
* @returns {string} the keyboard shortcut used for the id given * @returns {string} the keyboard shortcut used for the id given
*/ */
getShortcut: function (id) { _getShortcut: function (id) {
for (var key in shortcuts) { for (var key in _shortcuts) {
if (shortcuts.hasOwnProperty(key)) { if (_shortcuts.hasOwnProperty(key)) {
if (shortcuts[key].id === id) { if (_shortcuts[key].shortcutAttr === id) {
return " (" + shortcuts[key].character + ")"; return " (" + _shortcuts[key].character + ")";
} }
} }
} }
@ -135,7 +144,7 @@ var KeyboardShortcut = {
* @param e a KeyboardEvent * @param e a KeyboardEvent
* @returns {string} e.key or something close if not supported * @returns {string} e.key or something close if not supported
*/ */
getKeyboardKey: function (e) { _getKeyboardKey: function (e) {
if (typeof e.key === "string") { if (typeof e.key === "string") {
return e.key; return e.key;
} }
@ -156,6 +165,57 @@ var KeyboardShortcut = {
} else { } else {
return String.fromCharCode(e.which).toLowerCase(); return String.fromCharCode(e.which).toLowerCase();
} }
},
/**
* Adds the given shortcut to the help dialog.
*
* @param shortcutChar the shortcut character
* @param shortcutDescriptionKey the description of the shortcut
* @private
*/
_addShortcutToHelp: function (shortcutChar, shortcutDescriptionKey) {
var listElement = document.createElement("li");
listElement.id = shortcutChar;
var spanElement = document.createElement("span");
spanElement.className = "item-action";
var kbdElement = document.createElement("kbd");
kbdElement.className = "regular-key";
kbdElement.innerHTML = shortcutChar;
spanElement.appendChild(kbdElement);
var descriptionElement = document.createElement("span");
descriptionElement.className = "item-description";
descriptionElement.setAttribute("data-i18n", shortcutDescriptionKey);
descriptionElement.innerHTML
= APP.translation.translateString(shortcutDescriptionKey);
listElement.appendChild(spanElement);
listElement.appendChild(descriptionElement);
var parentListElement
= document.getElementById("keyboard-shortcuts-list");
if (parentListElement)
parentListElement.appendChild(listElement);
},
/**
* Removes the list element corresponding to the given shortcut from the
* help dialog
* @private
*/
_removeShortcutFromHelp: function (shortcutChar) {
var parentListElement
= document.getElementById("keyboard-shortcuts-list");
var shortcutElement = document.getElementById(shortcutChar);
if (shortcutElement)
parentListElement.removeChild(shortcutElement);
} }
}; };