github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/ui/app/components/global-search/control.js (about)

     1  import Component from '@ember/component';
     2  import { classNames } from '@ember-decorators/component';
     3  import { task } from 'ember-concurrency';
     4  import EmberObject, { action, computed, set } from '@ember/object';
     5  import { alias } from '@ember/object/computed';
     6  import { inject as service } from '@ember/service';
     7  import { debounce, run } from '@ember/runloop';
     8  import Searchable from 'nomad-ui/mixins/searchable';
     9  import classic from 'ember-classic-decorator';
    10  
    11  const SLASH_KEY = 191;
    12  const MAXIMUM_RESULTS = 10;
    13  
    14  @classNames('global-search-container')
    15  export default class GlobalSearchControl extends Component {
    16    @service dataCaches;
    17    @service router;
    18    @service store;
    19  
    20    searchString = null;
    21  
    22    constructor() {
    23      super(...arguments);
    24  
    25      this.jobSearch = JobSearch.create({
    26        dataSource: this,
    27      });
    28  
    29      this.nodeNameSearch = NodeNameSearch.create({
    30        dataSource: this,
    31      });
    32  
    33      this.nodeIdSearch = NodeIdSearch.create({
    34        dataSource: this,
    35      });
    36    }
    37  
    38    keyDownHandler(e) {
    39      const targetElementName = e.target.nodeName.toLowerCase();
    40  
    41      if (targetElementName != 'input' && targetElementName != 'textarea') {
    42        if (e.keyCode === SLASH_KEY) {
    43          e.preventDefault();
    44          this.open();
    45        }
    46      }
    47    }
    48  
    49    didInsertElement() {
    50      set(this, '_keyDownHandler', this.keyDownHandler.bind(this));
    51      document.addEventListener('keydown', this._keyDownHandler);
    52    }
    53  
    54    willDestroyElement() {
    55      document.removeEventListener('keydown', this._keyDownHandler);
    56    }
    57  
    58    @task(function*(string) {
    59      try {
    60        set(this, 'searchString', string);
    61  
    62        const jobs = yield this.dataCaches.fetch('job');
    63        const nodes = yield this.dataCaches.fetch('node');
    64  
    65        set(this, 'jobs', jobs.toArray());
    66        set(this, 'nodes', nodes.toArray());
    67  
    68        const jobResults = this.jobSearch.listSearched.slice(0, MAXIMUM_RESULTS);
    69  
    70        const mergedNodeListSearched = this.nodeIdSearch.listSearched.concat(this.nodeNameSearch.listSearched).uniq();
    71        const nodeResults = mergedNodeListSearched.slice(0, MAXIMUM_RESULTS);
    72  
    73        return [
    74          {
    75            groupName: resultsGroupLabel('Jobs', jobResults, this.jobSearch.listSearched),
    76            options: jobResults,
    77          },
    78          {
    79            groupName: resultsGroupLabel('Clients', nodeResults, mergedNodeListSearched),
    80            options: nodeResults,
    81          },
    82        ];
    83      } catch (e) {
    84        // eslint-disable-next-line
    85        console.log('exception searching', e);
    86      }
    87    })
    88    search;
    89  
    90    @action
    91    open() {
    92      if (this.select) {
    93        this.select.actions.open();
    94      }
    95    }
    96  
    97    @action
    98    selectOption(model) {
    99      const itemModelName = model.constructor.modelName;
   100  
   101      if (itemModelName === 'job') {
   102        this.router.transitionTo('jobs.job', model.plainId, {
   103          queryParams: { namespace: model.get('namespace.name') },
   104        });
   105      } else if (itemModelName === 'node') {
   106        this.router.transitionTo('clients.client', model.id);
   107      }
   108    }
   109  
   110    @action
   111    storeSelect(select) {
   112      if (select) {
   113        this.select = select;
   114      }
   115    }
   116  
   117    @action
   118    openOnClickOrTab(select, { target }) {
   119      // Bypass having to press enter to access search after clicking/tabbing
   120      const targetClassList = target.classList;
   121      const targetIsTrigger = targetClassList.contains('ember-power-select-trigger');
   122  
   123      // Allow tabbing out of search
   124      const triggerIsNotActive = !targetClassList.contains('ember-power-select-trigger--active');
   125  
   126      if (targetIsTrigger && triggerIsNotActive) {
   127        debounce(this, this.open, 150);
   128      }
   129    }
   130  
   131    @action
   132    onCloseEvent(select, event) {
   133      if (event.key === 'Escape') {
   134        run.next(() => {
   135          this.element.querySelector('.ember-power-select-trigger').blur();
   136        });
   137      }
   138    }
   139  
   140    calculatePosition(trigger) {
   141      const { top, left, width } = trigger.getBoundingClientRect();
   142      return {
   143        style: {
   144          left,
   145          width,
   146          top,
   147        },
   148      };
   149    }
   150  }
   151  
   152  @classic
   153  class JobSearch extends EmberObject.extend(Searchable) {
   154    @computed
   155    get searchProps() {
   156      return ['id', 'name'];
   157    }
   158  
   159    @computed
   160    get fuzzySearchProps() {
   161      return ['name'];
   162    }
   163  
   164    @alias('dataSource.jobs') listToSearch;
   165    @alias('dataSource.searchString') searchTerm;
   166  
   167    fuzzySearchEnabled = true;
   168    includeFuzzySearchMatches = true;
   169  }
   170  @classic
   171  class NodeNameSearch extends EmberObject.extend(Searchable) {
   172    @computed
   173    get searchProps() {
   174      return ['name'];
   175    }
   176  
   177    @computed
   178    get fuzzySearchProps() {
   179      return ['name'];
   180    }
   181  
   182    @alias('dataSource.nodes') listToSearch;
   183    @alias('dataSource.searchString') searchTerm;
   184  
   185    fuzzySearchEnabled = true;
   186    includeFuzzySearchMatches = true;
   187  }
   188  
   189  @classic
   190  class NodeIdSearch extends EmberObject.extend(Searchable) {
   191    @computed
   192    get regexSearchProps() {
   193      return ['id'];
   194    }
   195  
   196    @alias('dataSource.nodes') listToSearch;
   197    @computed('dataSource.searchString')
   198    get searchTerm() {
   199      return `^${this.get('dataSource.searchString')}`;
   200    }
   201  
   202    exactMatchEnabled = false;
   203    regexEnabled = true;
   204  }
   205  
   206  function resultsGroupLabel(type, renderedResults, allResults) {
   207    let countString;
   208  
   209    if (renderedResults.length < allResults.length) {
   210      countString = `showing ${renderedResults.length} of ${allResults.length}`;
   211    } else {
   212      countString = renderedResults.length;
   213    }
   214  
   215    return `${type} (${countString})`;
   216  }