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.

KeyboardShortcutsModal.tsx 4.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2020 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import * as React from 'react';
  21. import { Button } from 'sonar-ui-common/components/controls/buttons';
  22. import Modal from 'sonar-ui-common/components/controls/Modal';
  23. import { translate } from 'sonar-ui-common/helpers/l10n';
  24. const CATEGORIES = [
  25. {
  26. category: 'global',
  27. shortcuts: [
  28. { keys: ['s'], action: 'search' },
  29. { keys: ['?'], action: 'open_shortcuts' }
  30. ]
  31. },
  32. {
  33. category: 'code_page',
  34. shortcuts: [
  35. { keys: ['↑', '↓'], action: 'select_files' },
  36. { keys: ['→'], action: 'open_file' },
  37. { keys: ['←'], action: 'back' }
  38. ]
  39. },
  40. {
  41. category: 'issues_page',
  42. shortcuts: [
  43. { keys: ['↑', '↓'], action: 'navigate' },
  44. { keys: ['→'], action: 'source_code' },
  45. { keys: ['←'], action: 'back' },
  46. { keys: ['alt', '+', '↑', '↓'], action: 'navigate_locations' },
  47. { keys: ['alt', '+', '←', '→'], action: 'switch_flows' },
  48. { keys: ['f'], action: 'transition' },
  49. { keys: ['a'], action: 'assign' },
  50. { keys: ['m'], action: 'assign_to_me' },
  51. { keys: ['i'], action: 'severity' },
  52. { keys: ['c'], action: 'comment' },
  53. { keys: ['ctrl', '+', 'enter'], action: 'submit_comment' },
  54. { keys: ['t'], action: 'tags' }
  55. ]
  56. },
  57. {
  58. category: 'measures_page',
  59. shortcuts: [
  60. { keys: ['↑', '↓'], action: 'select_files' },
  61. { keys: ['→'], action: 'open_file' },
  62. { keys: ['←'], action: 'back' }
  63. ]
  64. },
  65. {
  66. category: 'rules_page',
  67. shortcuts: [
  68. { keys: ['↑', '↓'], action: 'navigate' },
  69. { keys: ['→'], action: 'rule_details' },
  70. { keys: ['←'], action: 'back' }
  71. ]
  72. }
  73. ];
  74. export default function KeyboardShortcutsModal() {
  75. const [display, setDisplay] = React.useState(false);
  76. React.useEffect(() => {
  77. const handleKeyPress = (event: KeyboardEvent) => {
  78. const { tagName } = event.target as HTMLElement;
  79. if (['INPUT', 'SELECT', 'TEXTAREA'].includes(tagName)) {
  80. return; // Ignore keys when typed in an input
  81. }
  82. if (event.key === '?') {
  83. setDisplay(d => !d);
  84. }
  85. };
  86. window.addEventListener('keypress', handleKeyPress);
  87. return () => {
  88. window.removeEventListener('keypress', handleKeyPress);
  89. };
  90. }, [setDisplay]);
  91. if (!display) {
  92. return null;
  93. }
  94. const title = translate('keyboard_shortcuts.title');
  95. return (
  96. <Modal contentLabel={title} onRequestClose={() => setDisplay(false)} size="medium">
  97. <div className="modal-head">
  98. <h2>{title}</h2>
  99. </div>
  100. <div className="modal-body modal-container markdown display-flex-wrap display-flex-space-between">
  101. {CATEGORIES.map(({ category, shortcuts }) => (
  102. <div key={category} className="spacer-right">
  103. <h3>{translate('keyboard_shortcuts', category, 'title')}</h3>
  104. <table>
  105. <thead>
  106. <tr>
  107. <th>{translate('keyboard_shortcuts.shortcut')}</th>
  108. <th>{translate('keyboard_shortcuts.action')}</th>
  109. </tr>
  110. </thead>
  111. <tbody>
  112. {shortcuts.map(({ action, keys }) => (
  113. <tr key={action}>
  114. <td>
  115. {keys.map(k =>
  116. k === '+' ? (
  117. <span key={k} className="little-spacer-right">
  118. {k}
  119. </span>
  120. ) : (
  121. <code key={k} className="little-spacer-right">
  122. {k}
  123. </code>
  124. )
  125. )}
  126. </td>
  127. <td>{translate('keyboard_shortcuts', category, action)}</td>
  128. </tr>
  129. ))}
  130. </tbody>
  131. </table>
  132. </div>
  133. ))}
  134. </div>
  135. <div className="modal-foot">
  136. <Button onClick={() => setDisplay(false)}>{translate('close')}</Button>
  137. </div>
  138. </Modal>
  139. );
  140. }