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