• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { BaseElement, element } from '../../../../base-ui/BaseElement';
17import { LitIcon } from '../../../../base-ui/icon/LitIcon';
18import { SearchHtml } from './Search.html';
19import '../../../../base-ui/select/LitSelect';
20import '../../../../base-ui/select/LitSelectOption';
21import { LitSelect } from '../../../../base-ui/select/LitSelect';
22import { Utils } from '../base/Utils';
23import { SpSystemTrace } from '../../SpSystemTrace';
24
25const LOCAL_STORAGE_SEARCH_KEY = 'search_key';
26let timerId: unknown = null;
27@element('lit-search')
28export class LitSearch extends BaseElement {
29  valueChangeHandler: ((str: string, id?: number) => void) | undefined | null;
30  private search: HTMLInputElement | undefined | null;
31  private _total: number = 0;
32  private _index: number = 0;
33  private _list: Array<unknown> = [];
34  private _value: boolean = false;
35  private totalEL: HTMLSpanElement | null | undefined;
36  private indexEL: HTMLSpanElement | null | undefined;
37  private searchHistoryListEL: HTMLUListElement | null | undefined;
38  private historyMaxCount = 100;
39  private lastSearch = '';
40  private searchList: Array<SearchInfo> = [];
41  private searchELList: Array<HTMLElement> = [];
42  //定义翻页index
43  private retarget_index: number = 0;
44  private _retarge_index: HTMLInputElement | null | undefined;
45  private traceSelector: LitSelect | null | undefined;
46  public currenSearchValue: string | undefined | null;
47
48  get list(): Array<unknown> {
49    return this._list;
50  }
51
52  set list(value: Array<unknown>) {
53    this._list = value;
54    this.total = value.length;
55  }
56
57  get index(): number {
58    return this._index;
59  }
60
61  set index(value: number) {
62    this._index = value;
63    this.indexEL!.textContent = `${value + 1}`;
64  }
65
66  get searchValue(): string {
67    return this.search?.value || '';
68  }
69
70  get total(): number {
71    return this._total;
72  }
73
74  set total(value: number) {
75    if (value > 0) {
76      this.setAttribute('show-search-info', '');
77    } else {
78      this.removeAttribute('show-search-info');
79    }
80    this._total = value;
81    this.totalEL!.textContent = value.toString();
82  }
83
84  get isLoading(): boolean {
85    return this.hasAttribute('isLoading');
86  }
87
88  set isLoading(va) {
89    if (va) {
90      this.setAttribute('isLoading', '');
91    } else {
92      this.removeAttribute('isLoading');
93      window.localStorage.setItem(LOCAL_STORAGE_SEARCH_KEY, '');
94    }
95  }
96
97  set isClearValue(value: boolean) {
98    this._value = value;
99  }
100
101  get isClearValue(): boolean {
102    return this._value;
103  }
104
105  setPercent(name: string = '', value: number): void {
106    let searchHide = this.shadowRoot!.querySelector<HTMLElement>('.root');
107    let searchIcon = this.shadowRoot!.querySelector<HTMLElement>('#search-icon');
108    if (this.hasAttribute('textRoll')) {
109      this.removeAttribute('textRoll');
110    }
111    this.isLoading = false;
112    if (value > 0 && value <= 100) {
113      searchHide!.style.display = 'flex';
114      searchHide!.style.backgroundColor = 'var(--dark-background5,#e3e3e3)';
115      searchIcon?.setAttribute('name', 'cloud-sync');
116      this.search!.setAttribute('placeholder', `${name}${value}%`);
117      this.search!.setAttribute('readonly', '');
118      this.search!.className = 'readonly';
119      this.isLoading = true;
120    } else if (value > 100) {
121      searchHide!.style.display = 'flex';
122      searchHide!.style.backgroundColor = 'var(--dark-background5,#fff)';
123      searchIcon?.setAttribute('name', 'search');
124      this.search?.setAttribute('placeholder', 'search');
125      this.search?.removeAttribute('readonly');
126      this.search!.className = 'write';
127    } else if (value === -1) {
128      searchHide!.style.display = 'flex';
129      searchHide!.style.backgroundColor = 'var(--dark-background5,#e3e3e3)';
130      searchIcon?.setAttribute('name', 'cloud-sync');
131      this.search!.setAttribute('placeholder', `${name}`);
132      this.search!.setAttribute('readonly', '');
133      this.search!.className = 'readonly';
134    } else if (value === -2) {
135      searchHide!.style.display = 'flex';
136      searchHide!.style.backgroundColor = 'var(--dark-background5,#e3e3e3)';
137      searchIcon?.setAttribute('name', 'cloud-sync');
138      this.search!.setAttribute('placeholder', `${name}`);
139      this.search!.setAttribute('readonly', '');
140      this.search!.className = 'text-Roll';
141      setTimeout((): void => {
142        this.setAttribute('textRoll', '');
143      }, 200);
144    } else if (value === -3) {
145      this.clear();
146      searchHide!.style.display = 'flex';
147      searchHide!.style.backgroundColor = 'var(--dark-background5,#e3e3e3)';
148      searchIcon?.setAttribute('name', 'search');
149      this.search!.setAttribute('placeholder', `${name}`);
150      this.search!.setAttribute('readonly', '');
151      this.search!.className = 'text-Roll';
152      setTimeout((): void => {
153        this.setAttribute('textRoll', '');
154      }, 200);
155    } else {
156      searchHide!.style.display = 'none';
157    }
158  }
159
160  clear(): void {
161    this.search = this.shadowRoot!.querySelector<HTMLInputElement>('input');
162    this.search!.value = '';
163    if (!Utils.isDistributedMode()) {
164      this.removeAttribute('distributed');
165    }
166    this.list = [];
167  }
168
169  blur(): void {
170    this.search?.blur();
171  }
172
173  updateSearchList(searchStr: string | null): void {
174    if (searchStr === null || searchStr.length === 0 || searchStr.trim().length === 0) {
175      return;
176    }
177    let searchInfo = this.searchList.find((searchInfo): boolean => searchInfo.searchContent === searchStr);
178    if (searchInfo !== undefined) {
179      let index = this.searchList.indexOf(searchInfo);
180      this.searchList.splice(index, 1);
181      this.searchList.unshift({ searchContent: searchStr, useCount: 1 });
182    } else {
183      this.searchList.unshift({ searchContent: searchStr, useCount: 1 });
184    }
185  }
186
187  getSearchHistory(): Array<SearchInfo> {
188    let searchString = window.localStorage.getItem(LOCAL_STORAGE_SEARCH_KEY);
189    if (searchString) {
190      let searHistory = JSON.parse(searchString);
191      if (Array.isArray(searHistory)) {
192        this.searchList = searHistory;
193        return searHistory;
194      }
195    }
196    return [];
197  }
198
199  private searchFocusListener(): void {
200    if (!this.search?.hasAttribute('readonly')) {
201      this.showSearchHistoryList();
202    }
203  }
204
205  private searchBlurListener(): void {
206    setTimeout((): void => {
207      this.hideSearchHistoryList();
208    }, 200);
209  }
210
211  private searchKeyupListener(e: unknown): void {
212    timerId = null;
213    // @ts-ignore
214    if (e.keyCode === 13) {
215      this.updateSearchList(this.search!.value);
216      // @ts-ignore
217      if (e.shiftKey) {
218        this.dispatchEvent(
219          new CustomEvent('previous-data', {
220            detail: {
221              value: this.search!.value,
222            },
223            composed: false,
224          })
225        );
226      } else {
227        this.dispatchEvent(
228          new CustomEvent('next-data', {
229            detail: {
230              value: this.search!.value,
231            },
232            composed: false,
233          })
234        );
235      }
236    } else {
237      this.updateSearchHistoryList(this.search!.value);
238      this.valueChangeHandler?.(this.trimSideSpace(this.search!.value));
239    }
240    // @ts-ignore
241    e.stopPropagation();
242  }
243
244  trimSideSpace(str: string): string {
245    return str.replace(/(^\s*)|(\s*$)/g, '');
246  }
247
248  initElements(): void {
249    this.initTraceSelectHandler();
250    this.search = this.shadowRoot!.querySelector<HTMLInputElement>('input');
251    this.totalEL = this.shadowRoot!.querySelector<HTMLSpanElement>('#total');
252    this.indexEL = this.shadowRoot!.querySelector<HTMLSpanElement>('#index');
253    this.searchHistoryListEL = this.shadowRoot!.querySelector<HTMLUListElement>('.search-history-list');
254    this._retarge_index = this.shadowRoot!.querySelector<HTMLInputElement>("input[name='retarge_index']");
255    this.search!.addEventListener('focus', (): void => {
256      this.searchFocusListener();
257    });
258    this.search!.addEventListener('blur', (): void => {
259      this.searchBlurListener();
260    });
261    this.search!.addEventListener('keyup', (e: KeyboardEvent) => {
262      this._retarge_index!.value = '';
263      this.searchKeyupListener(e);
264    });
265    //阻止事件冒泡
266    this.search!.addEventListener('keydown', (e: KeyboardEvent) => {
267      e.stopPropagation();
268    });
269
270    this.search!.addEventListener('keypress', (e: KeyboardEvent) => {
271      e.stopPropagation();
272    });
273    this.shadowRoot?.querySelector('#arrow-left')?.addEventListener('click', (): void => {
274      this.dispatchEvent(
275        new CustomEvent('previous-data', {
276          detail: {
277            value: this.search!.value,
278          },
279        })
280      );
281    });
282    this.shadowRoot?.querySelector('#arrow-right')?.addEventListener('click', (): void => {
283      this.dispatchEvent(
284        new CustomEvent('next-data', {
285          detail: {
286            value: this.search!.value,
287          },
288        })
289      );
290    });
291    this.keyUpListener();
292    //阻止事件冒泡
293    this.shadowRoot?.querySelector("input[name='retarge_index']")?.addEventListener('keydown', (e: unknown) => {
294      // @ts-ignore
295      e.stopPropagation();
296    });
297    this.shadowRoot?.querySelector("input[name='retarge_index']")?.addEventListener('keypress', (e: unknown) => {
298      // @ts-ignore
299      e.stopPropagation();
300    });
301  }
302
303  private initTraceSelectHandler(): void {
304    this.traceSelector = this.shadowRoot!.querySelector<LitSelect>('#trace_selector');
305    let selectorBody = this.traceSelector?.shadowRoot!.querySelector<HTMLDivElement>('.body');
306    if (selectorBody) {
307      selectorBody.style.width = '200px';
308      selectorBody.style.overflow = 'hidden';
309    }
310    this.traceSelector?.addEventListener('change', (): void => {
311      if (Utils.currentSelectTrace !== this.traceSelector!.value) {
312        Utils.currentSelectTrace = this.traceSelector!.value;
313        this.clear();
314        this.dispatchEvent(new CustomEvent('trace-change', {
315          detail: {
316            value: this.traceSelector?.value,
317          },
318        }));
319      }
320    });
321    this.traceSelector?.addEventListener('focus', (e): void => {
322      e.stopPropagation();
323    });
324  }
325
326  setTraceSelectOptions(): void {
327    this.traceSelector!.dataSource = Utils.distributedTrace.map((trace, index) => ({
328      value: `${index + 1}`,
329      name: trace
330    }));
331  }
332
333  getSearchTraceId(): string | null | undefined {
334    if (this.hasAttribute('distributed')) {
335      return this.traceSelector?.value;
336    }
337    return null;
338  }
339
340  private keyUpListener(): void {
341    let _root = this.shadowRoot!.querySelector<HTMLInputElement>('.root');
342    let _prompt = this.shadowRoot!.querySelector<HTMLInputElement>('#prompt');
343    // 添加翻页监听事件
344    this.shadowRoot?.querySelector("input[name='retarge_index']")?.addEventListener('keyup', (e: unknown): void => {
345      // @ts-ignore
346      if (e.keyCode === 13) {
347        this.retarget_index = Number(this._retarge_index!.value);
348        if (this.retarget_index <= this._list.length && this.retarget_index !== 0) {
349          this.dispatchEvent(
350            new CustomEvent('retarget-data', {
351              detail: {
352                value: this.retarget_index,
353              },
354            })
355          );
356        } else if (this.retarget_index === 0) {
357          return;
358        } else {
359          _prompt!.style.display = 'block';
360          _root!.style.display = 'none';
361          _prompt!.innerHTML = `${this._list.length} pages in total, please re-enter`;
362          setTimeout(() => {
363            _prompt!.style.display = 'none';
364            _root!.style.display = 'flex';
365            this._retarge_index!.value = '';
366          }, 2000);
367        }
368        // @ts-ignore
369        e.target.blur();
370      }
371      // @ts-ignore
372      e.stopPropagation();
373    });
374  }
375
376  initHtml(): string {
377    return SearchHtml;
378  }
379
380  showSearchHistoryList(): void {
381    this.searchHistoryListEL!.innerHTML = '';
382    let historyInfos = this.getSearchHistory();
383    let fragment = document.createElement('div');
384    historyInfos.forEach((historyInfo) => {
385      let searchVessel = document.createElement('div');
386      searchVessel.className = 'search-list';
387      let searchInfoOption = document.createElement('li');
388      let closeOption = document.createElement('lit-icon');
389      closeOption.setAttribute('name', 'close');
390      closeOption.className = 'close-option';
391      closeOption.setAttribute('size', '20');
392      searchInfoOption.className = 'search-history-list-item';
393      searchInfoOption.textContent = historyInfo.searchContent;
394      searchInfoOption.addEventListener('click', (): void => {
395        if (searchInfoOption.textContent) {
396          let flag = this.search!.value;
397          this.search!.value = searchInfoOption.textContent;
398          this.valueChangeHandler?.(this.search!.value);
399          if (flag !== searchInfoOption.textContent) {
400            this._retarge_index!.value = '';
401            this.index = -1;
402          }
403        }
404      });
405      searchVessel.append(searchInfoOption);
406      searchVessel.append(closeOption);
407      this.searchELList.push(searchInfoOption);
408      this.searchELList.push(closeOption);
409      fragment.append(searchVessel);
410    });
411    this.searchHistoryListEL?.append(fragment);
412    if (this.searchList.length > 0) {
413      this.searchHistoryListEL!.style.display = 'block';
414    }
415    let closeOptionList = this.searchHistoryListEL!.querySelectorAll<LitIcon>('.close-option');
416    closeOptionList.forEach((item): void => {
417      item.addEventListener('click', (): void => {
418        let currentHistory = item.previousSibling!.textContent;
419        let index = this.searchList.findIndex((element): boolean => element.searchContent === currentHistory);
420        if (index !== -1) {
421          this.searchList.splice(index, 1);
422        }
423        let historyStr = JSON.stringify(this.searchList);
424        window.localStorage.setItem(LOCAL_STORAGE_SEARCH_KEY, historyStr);
425      });
426    });
427  }
428
429  hideSearchHistoryList(): void {
430    this.searchHistoryListEL!.style.display = 'none';
431    if (this.searchList.length > this.historyMaxCount) {
432      this.searchList = this.searchList.slice(0, this.historyMaxCount);
433    }
434    if (this.searchList.length === 0) {
435      return;
436    }
437    let historyStr = JSON.stringify(this.searchList);
438    window.localStorage.setItem(LOCAL_STORAGE_SEARCH_KEY, historyStr);
439    this.searchList = [];
440    this.searchELList = [];
441  }
442
443  updateSearchHistoryList(searchValue: string): void {
444    const keyword = searchValue.toLowerCase();
445    this.searchELList.forEach((item) => {
446      if (item.textContent!.toLowerCase().includes(keyword)) {
447        item.style.display = 'block';
448      } else {
449        item.style.display = 'none';
450      }
451    });
452  }
453}
454
455export interface SearchInfo {
456  searchContent: string;
457  useCount: number;
458}
459