• 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) => 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 {
145      searchHide!.style.display = 'none';
146    }
147  }
148
149  clear(): void {
150    this.search = this.shadowRoot!.querySelector<HTMLInputElement>('input');
151    this.search!.value = '';
152    if (!Utils.isDistributedMode()) {
153      this.removeAttribute('distributed');
154    }
155    this.list = [];
156  }
157
158  blur(): void {
159    this.search?.blur();
160  }
161
162  updateSearchList(searchStr: string | null): void {
163    if (searchStr === null || searchStr.length === 0 || searchStr.trim().length === 0) {
164      return;
165    }
166    let searchInfo = this.searchList.find((searchInfo): boolean => searchInfo.searchContent === searchStr);
167    if (searchInfo !== undefined) {
168      let index = this.searchList.indexOf(searchInfo);
169      this.searchList.splice(index, 1);
170      this.searchList.unshift({ searchContent: searchStr, useCount: 1 });
171    } else {
172      this.searchList.unshift({ searchContent: searchStr, useCount: 1 });
173    }
174  }
175
176  getSearchHistory(): Array<SearchInfo> {
177    let searchString = window.localStorage.getItem(LOCAL_STORAGE_SEARCH_KEY);
178    if (searchString) {
179      let searHistory = JSON.parse(searchString);
180      if (Array.isArray(searHistory)) {
181        this.searchList = searHistory;
182        return searHistory;
183      }
184    }
185    return [];
186  }
187
188  private searchFocusListener(): void {
189    if (!this.search?.hasAttribute('readonly')) {
190      this.showSearchHistoryList();
191    }
192  }
193
194  private searchBlurListener(): void {
195    setTimeout((): void => {
196      this.hideSearchHistoryList();
197    }, 200);
198  }
199
200  private searchKeyupListener(e: KeyboardEvent): void {
201    timerId = null;
202    if (e.code === 'Enter' || e.code === 'NumpadEnter') {
203      this.updateSearchList(this.search!.value);
204      if (e.shiftKey) {
205        this.dispatchEvent(
206          new CustomEvent('previous-data', {
207            detail: {
208              value: this.search!.value,
209            },
210            composed: false,
211          })
212        );
213      } else {
214        this.dispatchEvent(
215          new CustomEvent('next-data', {
216            detail: {
217              value: this.search!.value,
218            },
219            composed: false,
220          })
221        );
222      }
223    } else {
224      this.updateSearchHistoryList(this.search!.value);
225      this.valueChangeHandler?.(this.trimSideSpace(this.search!.value));
226    }
227    e.stopPropagation();
228  }
229
230  trimSideSpace(str: string): string {
231    return str.replace(/(^\s*)|(\s*$)/g, '');
232  }
233
234  initElements(): void {
235    this.initTraceSelectHandler();
236    this.search = this.shadowRoot!.querySelector<HTMLInputElement>('input');
237    this.totalEL = this.shadowRoot!.querySelector<HTMLSpanElement>('#total');
238    this.indexEL = this.shadowRoot!.querySelector<HTMLSpanElement>('#index');
239    this.searchHistoryListEL = this.shadowRoot!.querySelector<HTMLUListElement>('.search-history-list');
240    this._retarge_index = this.shadowRoot!.querySelector<HTMLInputElement>("input[name='retarge_index']");
241    this.search!.addEventListener('focus', (): void => {
242      this.searchFocusListener();
243    });
244    this.search!.addEventListener('blur', (): void => {
245      this.searchBlurListener();
246    });
247    this.search!.addEventListener('keyup', (e: KeyboardEvent) => {
248      SpSystemTrace.isKeyUp = true;
249      this._retarge_index!.value = '';
250      this.searchKeyupListener(e);
251    });
252    //阻止事件冒泡
253    this.search!.addEventListener('keydown', (e: KeyboardEvent) => {
254      SpSystemTrace.isKeyUp = false;
255      e.stopPropagation();
256    });
257
258    this.search!.addEventListener('keypress', (e: KeyboardEvent) => {
259      SpSystemTrace.isKeyUp = false;
260      e.stopPropagation();
261    });
262    this.shadowRoot?.querySelector('#arrow-left')?.addEventListener('click', (): void => {
263      this.dispatchEvent(
264        new CustomEvent('previous-data', {
265          detail: {
266            value: this.search!.value,
267          },
268        })
269      );
270    });
271    this.shadowRoot?.querySelector('#arrow-right')?.addEventListener('click', (): void => {
272      this.dispatchEvent(
273        new CustomEvent('next-data', {
274          detail: {
275            value: this.search!.value,
276          },
277        })
278      );
279    });
280    this.keyUpListener();
281    //阻止事件冒泡
282    this.shadowRoot?.querySelector("input[name='retarge_index']")?.addEventListener('keydown', (e: unknown) => {
283      SpSystemTrace.isKeyUp = false;
284      // @ts-ignore
285      e.stopPropagation();
286    });
287    this.shadowRoot?.querySelector("input[name='retarge_index']")?.addEventListener('keypress', (e: unknown) => {
288      SpSystemTrace.isKeyUp = false;
289      // @ts-ignore
290      e.stopPropagation();
291    });
292  }
293
294  private initTraceSelectHandler(): void {
295    this.traceSelector = this.shadowRoot!.querySelector<LitSelect>('#trace_selector');
296    let selectorBody = this.traceSelector?.shadowRoot!.querySelector<HTMLDivElement>('.body');
297    if (selectorBody) {
298      selectorBody.style.width = '200px';
299      selectorBody.style.overflow = 'hidden';
300    }
301    this.traceSelector?.addEventListener('change', (): void => {
302      if (Utils.currentSelectTrace !== this.traceSelector!.value) {
303        Utils.currentSelectTrace = this.traceSelector!.value;
304        this.clear();
305        this.dispatchEvent(new CustomEvent('trace-change', {
306          detail: {
307            value: this.traceSelector?.value,
308          },
309        }));
310      }
311    });
312    this.traceSelector?.addEventListener('focus', (e): void => {
313      e.stopPropagation();
314    });
315  }
316
317  setTraceSelectOptions(): void {
318    this.traceSelector!.dataSource = Utils.distributedTrace.map((trace, index) => ({
319      value: `${index + 1}`,
320      name: trace
321    }));
322  }
323
324  getSearchTraceId(): string | null | undefined {
325    if (this.hasAttribute('distributed')) {
326      return this.traceSelector?.value;
327    }
328    return undefined;
329  }
330
331  private keyUpListener(): void {
332    let _root = this.shadowRoot!.querySelector<HTMLInputElement>('.root');
333    let _prompt = this.shadowRoot!.querySelector<HTMLInputElement>('#prompt');
334    // 添加翻页监听事件
335    this.shadowRoot?.querySelector("input[name='retarge_index']")?.addEventListener('keyup', (e: unknown): void => {
336      // @ts-ignore
337      if (e.keyCode === 13) {
338        this.retarget_index = Number(this._retarge_index!.value);
339        if (this.retarget_index <= this._list.length && this.retarget_index !== 0) {
340          this.dispatchEvent(
341            new CustomEvent('retarget-data', {
342              detail: {
343                value: this.retarget_index,
344              },
345            })
346          );
347        } else if (this.retarget_index === 0) {
348          return;
349        } else {
350          _prompt!.style.display = 'block';
351          _root!.style.display = 'none';
352          _prompt!.innerHTML = `${this._list.length} pages in total, please re-enter`;
353          setTimeout(() => {
354            _prompt!.style.display = 'none';
355            _root!.style.display = 'flex';
356            this._retarge_index!.value = '';
357          }, 2000);
358        }
359        // @ts-ignore
360        e.target.blur();
361      }
362      // @ts-ignore
363      e.stopPropagation();
364    });
365  }
366
367  initHtml(): string {
368    return SearchHtml;
369  }
370
371  showSearchHistoryList(): void {
372    this.searchHistoryListEL!.innerHTML = '';
373    let historyInfos = this.getSearchHistory();
374    let fragment = document.createElement('div');
375    historyInfos.forEach((historyInfo) => {
376      let searchVessel = document.createElement('div');
377      searchVessel.className = 'search-list';
378      let searchInfoOption = document.createElement('li');
379      let closeOption = document.createElement('lit-icon');
380      closeOption.setAttribute('name', 'close');
381      closeOption.className = 'close-option';
382      closeOption.setAttribute('size', '20');
383      searchInfoOption.className = 'search-history-list-item';
384      searchInfoOption.textContent = historyInfo.searchContent;
385      searchInfoOption.addEventListener('click', (): void => {
386        if (searchInfoOption.textContent) {
387          let flag = this.search!.value;
388          this.search!.value = searchInfoOption.textContent;
389          this.valueChangeHandler?.(this.search!.value);
390          if (flag !== searchInfoOption.textContent) {
391            this._retarge_index!.value = '';
392            this.index = -1;
393          }
394        }
395      });
396      searchVessel.append(searchInfoOption);
397      searchVessel.append(closeOption);
398      this.searchELList.push(searchInfoOption);
399      this.searchELList.push(closeOption);
400      fragment.append(searchVessel);
401    });
402    this.searchHistoryListEL?.append(fragment);
403    if (this.searchList.length > 0) {
404      this.searchHistoryListEL!.style.display = 'block';
405    }
406    let closeOptionList = this.searchHistoryListEL!.querySelectorAll<LitIcon>('.close-option');
407    closeOptionList.forEach((item): void => {
408      item.addEventListener('click', (): void => {
409        let currentHistory = item.previousSibling!.textContent;
410        let index = this.searchList.findIndex((element): boolean => element.searchContent === currentHistory);
411        if (index !== -1) {
412          this.searchList.splice(index, 1);
413        }
414        let historyStr = JSON.stringify(this.searchList);
415        window.localStorage.setItem(LOCAL_STORAGE_SEARCH_KEY, historyStr);
416      });
417    });
418  }
419
420  hideSearchHistoryList(): void {
421    this.searchHistoryListEL!.style.display = 'none';
422    if (this.searchList.length > this.historyMaxCount) {
423      this.searchList = this.searchList.slice(0, this.historyMaxCount);
424    }
425    if (this.searchList.length === 0) {
426      return;
427    }
428    let historyStr = JSON.stringify(this.searchList);
429    window.localStorage.setItem(LOCAL_STORAGE_SEARCH_KEY, historyStr);
430    this.searchList = [];
431    this.searchELList = [];
432  }
433
434  updateSearchHistoryList(searchValue: string): void {
435    const keyword = searchValue.toLowerCase();
436    this.searchELList.forEach((item) => {
437      if (item.textContent!.toLowerCase().includes(keyword)) {
438        item.style.display = 'block';
439      } else {
440        item.style.display = 'none';
441      }
442    });
443  }
444}
445
446export interface SearchInfo {
447  searchContent: string;
448  useCount: number;
449}
450