search.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import { ReactWidget } from '@jupyterlab/apputils';
  4. import { Widget, PanelLayout } from '@phosphor/widgets';
  5. import React, { useState, useRef, useEffect } from 'react';
  6. import { Variables } from '../index';
  7. export class Search extends Widget {
  8. constructor(model: Variables.IModel) {
  9. super();
  10. this.addClass('jp-DebuggerVariables-search');
  11. const layout = new PanelLayout();
  12. this.layout = layout;
  13. this.scope = new ScopeSearch(model);
  14. this.search = new SearchInput(model);
  15. layout.addWidget(this.scope);
  16. layout.addWidget(this.search);
  17. }
  18. readonly scope: Widget;
  19. readonly search: Widget;
  20. }
  21. const SearchComponent = ({ model }: any) => {
  22. const [state, setState] = useState('');
  23. model.filter = state;
  24. return (
  25. <div>
  26. <span className="fa fa-search"></span>
  27. <input
  28. placeholder="Search..."
  29. value={state}
  30. onChange={e => {
  31. setState(e.target.value);
  32. }}
  33. />
  34. </div>
  35. );
  36. };
  37. class SearchInput extends ReactWidget {
  38. search: string;
  39. model: Variables.IModel;
  40. constructor(model: Variables.IModel) {
  41. super();
  42. this.model = model;
  43. this.search = model.filter;
  44. this.node.style;
  45. this.addClass('jp-DebuggerVariables-input');
  46. }
  47. render() {
  48. return <SearchComponent model={this.model} />;
  49. }
  50. }
  51. class ScopeSearch extends ReactWidget {
  52. constructor(model: Variables.IModel) {
  53. super();
  54. this.model = model;
  55. this.node.style.overflow = 'visible';
  56. this.node.style.width = '85px';
  57. this.addClass('jp-DebuggerVariables-scope');
  58. }
  59. model: Variables.IModel;
  60. render() {
  61. return <ScopeMenuComponent model={this.model} />;
  62. }
  63. }
  64. const useOutsideClick = (
  65. ref: React.MutableRefObject<any>,
  66. callback: Function
  67. ) => {
  68. const handleClickOutside = (e: Event) => {
  69. if (ref.current && !ref.current.contains(e.target)) {
  70. callback();
  71. }
  72. };
  73. useEffect(() => {
  74. document.addEventListener('mousedown', handleClickOutside);
  75. return () => {
  76. document.removeEventListener('mousedown', handleClickOutside);
  77. };
  78. });
  79. };
  80. const ScopeMenuComponent = ({ model }: { model: Variables.IModel }) => {
  81. const [toggleState, setToggle] = useState(false);
  82. const [scopes, setScopes] = useState(model.scopes);
  83. const [scope, setScope] = useState(model.currentScope);
  84. const wrapperRef = useRef(null);
  85. useEffect(() => {
  86. const updateScopes = (_: Variables.IModel, updates: Variables.IScope[]) => {
  87. const scope = !!updates && updates.length > 0 ? updates[0] : null;
  88. setScopes(updates);
  89. setScope(scope);
  90. };
  91. model.scopesChanged.connect(updateScopes);
  92. return () => {
  93. model.scopesChanged.disconnect(updateScopes);
  94. };
  95. });
  96. const onClickOutSide = () => {
  97. setToggle(false);
  98. };
  99. const toggle = () => {
  100. if (!!scopes) {
  101. setToggle(!toggleState);
  102. }
  103. };
  104. useOutsideClick(wrapperRef, onClickOutSide);
  105. const changeScope = (newScope: Variables.IScope) => {
  106. if (newScope === scope) {
  107. return;
  108. }
  109. setScope(newScope);
  110. model.currentScope = newScope;
  111. setToggle(false);
  112. };
  113. const List = (
  114. <ul>
  115. {!!scopes
  116. ? scopes.map(scope => (
  117. <li key={scope.name} onClick={e => changeScope(scope)}>
  118. {scope.name}
  119. </li>
  120. ))
  121. : null}
  122. </ul>
  123. );
  124. return (
  125. <div onClick={e => toggle()} ref={wrapperRef}>
  126. <span className="label">{scope ? scope.name : '-'}</span>
  127. <span className="fa fa-caret-down"></span>
  128. {toggleState ? List : null}
  129. </div>
  130. );
  131. };