ContextMenu.jsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import {Component} from 'react';
  2. import cls from 'classnames';
  3. import {store} from '../store';
  4. import {elementIsOutside} from '../utils';
  5. import s from './ContextMenu.css';
  6. import ContextMenuItem from './ContextMenuItem';
  7. export default class ContextMenu extends Component {
  8. componentDidMount() {
  9. this.boundingRect = this.node.getBoundingClientRect();
  10. }
  11. componentDidUpdate(prevProps) {
  12. if (this.props.visible && !prevProps.visible) {
  13. document.addEventListener('mousedown', this.handleDocumentMousedown, true);
  14. } else if (prevProps.visible && !this.props.visible) {
  15. document.removeEventListener('mousedown', this.handleDocumentMousedown, true);
  16. }
  17. }
  18. handleClickHideChunk = () => {
  19. const {chunk: selectedChunk} = this.props;
  20. if (selectedChunk && selectedChunk.label) {
  21. const filteredChunks = store.selectedChunks.filter(
  22. chunk => chunk.label !== selectedChunk.label
  23. );
  24. store.selectedChunks = filteredChunks;
  25. }
  26. this.hide();
  27. };
  28. handleClickFilterToChunk = () => {
  29. const {chunk: selectedChunk} = this.props;
  30. if (selectedChunk && selectedChunk.label) {
  31. const filteredChunks = store.allChunks.filter(
  32. chunk => chunk.label === selectedChunk.label
  33. );
  34. store.selectedChunks = filteredChunks;
  35. }
  36. this.hide();
  37. };
  38. handleClickShowAllChunks = () => {
  39. store.selectedChunks = store.allChunks;
  40. this.hide();
  41. };
  42. /**
  43. * Handle document-wide `mousedown` events to detect clicks
  44. * outside the context menu.
  45. * @param {MouseEvent} e - DOM mouse event object
  46. * @returns {void}
  47. */
  48. handleDocumentMousedown = e => {
  49. const isSecondaryClick = e.ctrlKey || e.button === 2;
  50. if (!isSecondaryClick && elementIsOutside(e.target, this.node)) {
  51. e.preventDefault();
  52. e.stopPropagation();
  53. this.hide();
  54. }
  55. };
  56. hide() {
  57. if (this.props.onHide) {
  58. this.props.onHide();
  59. }
  60. }
  61. saveNode = node => (this.node = node);
  62. getStyle() {
  63. const {boundingRect} = this;
  64. // Upon the first render of this component, we don't yet know
  65. // its dimensions, so can't position it yet
  66. if (!boundingRect) {
  67. return;
  68. }
  69. const {coords} = this.props;
  70. const pos = {
  71. left: coords.x,
  72. top: coords.y,
  73. };
  74. if (pos.left + boundingRect.width > window.innerWidth) {
  75. // Shifting horizontally
  76. pos.left = window.innerWidth - boundingRect.width;
  77. }
  78. if (pos.top + boundingRect.height > window.innerHeight) {
  79. // Flipping vertically
  80. pos.top = coords.y - boundingRect.height;
  81. }
  82. // ts-ignore
  83. return pos;
  84. }
  85. render() {
  86. const {visible} = this.props;
  87. const containerClassName = cls({
  88. [s.container]: true,
  89. [s.hidden]: !visible,
  90. });
  91. const multipleChunksSelected = store.selectedChunks.length > 1;
  92. return (
  93. <ul ref={this.saveNode} className={containerClassName} style={this.getStyle()}>
  94. <ContextMenuItem
  95. disabled={!multipleChunksSelected}
  96. onClick={this.handleClickHideChunk}
  97. >
  98. Hide chunk
  99. </ContextMenuItem>
  100. <ContextMenuItem
  101. disabled={!multipleChunksSelected}
  102. onClick={this.handleClickFilterToChunk}
  103. >
  104. Hide all other chunks
  105. </ContextMenuItem>
  106. <hr />
  107. <ContextMenuItem
  108. disabled={store.allChunksSelected}
  109. onClick={this.handleClickShowAllChunks}
  110. >
  111. Show all chunks
  112. </ContextMenuItem>
  113. </ul>
  114. );
  115. }
  116. }