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