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 { TimeRuler } from './timer-shaft/TimeRuler'; 18import { Rect } from './timer-shaft/Rect'; 19import { RangeRuler, TimeRange } from './timer-shaft/RangeRuler'; 20import { SlicesTime, SportRuler } from './timer-shaft/SportRuler'; 21import { procedurePool } from '../../database/Procedure'; 22import { Flag } from './timer-shaft/Flag'; 23import { info } from '../../../log/Log'; 24import { TraceSheet } from './base/TraceSheet'; 25import { SelectionParam } from '../../bean/BoxSelection'; 26import { type SpSystemTrace, CurrentSlicesTime } from '../SpSystemTrace'; 27import './timer-shaft/CollapseButton'; 28import { TimerShaftElementHtml } from './TimerShaftElement.html'; 29import { SpChartList } from './SpChartList'; 30//随机生成十六位进制颜色 31//@ts-ignore 32export function randomRgbColor(): string { 33 let r = Math.floor(Math.random() * 255); 34 let g = Math.floor(Math.random() * 255); 35 let b = Math.floor(Math.random() * 255); 36 let color = ''; 37 if (r * 0.299 + g * 0.587 + b * 0.114 < 192) { 38 let r16 = r.toString(16).length === 1 && r.toString(16) <= 'f' ? 0 + r.toString(16) : r.toString(16); 39 let g16 = g.toString(16).length === 1 && g.toString(16) <= 'f' ? 0 + g.toString(16) : g.toString(16); 40 let b16 = b.toString(16).length === 1 && b.toString(16) <= 'f' ? 0 + b.toString(16) : b.toString(16); 41 color = '#' + r16 + g16 + b16; 42 } else { 43 randomRgbColor(); 44 } 45 return color; 46} 47 48export function ns2s(ns: number): string { 49 let oneSecond = 1_000_000_000; // 1 second 50 let oneMillisecond = 1_000_000; // 1 millisecond 51 let oneMicrosecond = 1_000; // 1 microsecond 52 let nanosecond1 = 1000.0; 53 let result; 54 if (ns >= oneSecond) { 55 result = (ns / 1000 / 1000 / 1000).toFixed(1) + ' s'; 56 } else if (ns >= oneMillisecond) { 57 result = (ns / 1000 / 1000).toFixed(1) + ' ms'; 58 } else if (ns >= oneMicrosecond) { 59 result = (ns / 1000).toFixed(1) + ' μs'; 60 } else if (ns > 0) { 61 result = ns.toFixed(1) + ' ns'; 62 } else { 63 result = ns.toFixed(1) + ' s'; 64 } 65 return result; 66} 67 68export function ns2UnitS(ns: number, scale: number): string { 69 let oneSecond = 1_000_000_000; // 1 second 70 let result; 71 if (scale >= 10_000_000_000) { 72 result = (ns / oneSecond).toFixed(0) + ' s'; 73 } else if (scale >= 1_000_000_000) { 74 result = (ns / oneSecond).toFixed(1) + ' s'; 75 } else if (scale >= 100_000_000) { 76 result = (ns / oneSecond).toFixed(2) + ' s'; 77 } else if (scale >= 10_000_000) { 78 result = (ns / oneSecond).toFixed(3) + ' s'; 79 } else if (scale >= 1_000_000) { 80 result = (ns / oneSecond).toFixed(4) + ' s'; 81 } else if (scale >= 100_000) { 82 result = (ns / oneSecond).toFixed(5) + ' s'; 83 } else { 84 result = (ns / oneSecond).toFixed(6) + ' s'; 85 } 86 return result; 87} 88 89export function ns2x(ns: number, startNS: number, endNS: number, duration: number, rect: Rect): number { 90 if (endNS === 0) { 91 endNS = duration; 92 } 93 let xSize: number = ((ns - startNS) * rect.width) / (endNS - startNS); 94 if (xSize < 0) { 95 xSize = 0; 96 } 97 if (xSize > rect.width) { 98 xSize = rect.width; 99 } 100 return xSize; 101} 102 103@element('timer-shaft-element') 104export class TimerShaftElement extends BaseElement { 105 // @ts-ignore 106 offscreen: OffscreenCanvas | undefined; 107 isOffScreen: boolean = false; 108 public ctx: CanvasRenderingContext2D | undefined | null; 109 public canvas: HTMLCanvasElement | null | undefined; 110 public totalEL: HTMLDivElement | null | undefined; 111 public timeTotalEL: HTMLSpanElement | null | undefined; 112 public timeOffsetEL: HTMLSpanElement | null | undefined; 113 public collectGroup: HTMLDivElement | null | undefined; 114 public collect1: HTMLInputElement | null | undefined; 115 public loadComplete: boolean = false; 116 public collecBtn: HTMLElement | null | undefined; 117 rangeChangeHandler: ((timeRange: TimeRange) => void) | undefined = undefined; 118 rangeClickHandler: ((sliceTime: SlicesTime | undefined | null) => void) | undefined = undefined; 119 flagChangeHandler: ((hoverFlag: Flag | undefined | null, selectFlag: Flag | undefined | null) => void) | undefined = 120 undefined; 121 flagClickHandler: ((flag: Flag | undefined | null) => void) | undefined = undefined; 122 /** 123 * 离线渲染需要的变量 124 */ 125 dpr = window.devicePixelRatio || 1; 126 frame: Rect = new Rect(0, 0, 0, 0); 127 must: boolean = true; 128 hoverX: number = 0; 129 hoverY: number = 0; 130 canvasWidth: number = 0; 131 canvasHeight: number = 0; 132 _cpuUsage: Array<{ cpu: number; ro: number; rate: number }> = []; 133 protected timeRuler: TimeRuler | undefined; 134 protected _rangeRuler: RangeRuler | undefined; 135 protected _sportRuler: SportRuler | undefined; 136 private root: HTMLDivElement | undefined | null; 137 private _totalNS: number = 10_000_000_000; 138 private _startNS: number = 0; 139 private _endNS: number = 10_000_000_000; 140 private traceSheetEL: TraceSheet | undefined | null; 141 private sliceTime: SlicesTime | undefined | null; 142 public selectionList: Array<SelectionParam> = []; 143 public selectionMap: Map<string, SelectionParam> = new Map<string, SelectionParam>(); 144 public usageEL: HTMLDivElement | null | undefined; 145 public timerShaftEL: TimerShaftElement | null | undefined; 146 public rowsPaneEL: HTMLDivElement | null | undefined; 147 public favoriteChartListEL: SpChartList | undefined | null; 148 _checkExpand: boolean = false; //是否展开 149 _usageFoldHeight: number = 56.25; //初始化时折叠的负载区高度 150 usageExpandHeight: number = 75; //给定的展开的负载区高度 151 _cpuUsageCount: Array<{ cpu: number; ro: number; rate: number }> = []; 152 153 get sportRuler(): SportRuler | undefined { 154 return this._sportRuler; 155 } 156 157 get rangeRuler(): RangeRuler | undefined { 158 return this._rangeRuler; 159 } 160 161 set cpuUsage(value: Array<{ cpu: number; ro: number; rate: number }>) { 162 info('set cpuUsage values :', value); 163 this._cpuUsage = value; 164 165 this._cpuUsageCount = value; 166 if (this._cpuUsageCount.length) { 167 this.usageEL!.innerHTML = 'CPU Usage'; 168 } 169 170 if (this._rangeRuler) { 171 this._rangeRuler.cpuUsage = this._cpuUsage; 172 } 173 } 174 175 get checkExpand(): boolean { 176 return this._checkExpand; 177 } 178 179 set checkExpand(value: boolean) { 180 this._checkExpand = value; 181 } 182 183 get usageFoldHeight(): number { 184 return this._usageFoldHeight; 185 } 186 set usageFoldHeight(value: number) { 187 this._usageFoldHeight = value; 188 } 189 190 get totalNS(): number { 191 return this._totalNS; 192 } 193 194 set totalNS(value: number) { 195 info('set totalNS values :', value); 196 this._totalNS = value; 197 if (this.timeRuler) { 198 this.timeRuler.totalNS = value; 199 } 200 if (this._rangeRuler) { 201 this._rangeRuler.range.totalNS = value; 202 } 203 if (this.timeTotalEL) { 204 this.timeTotalEL.textContent = `${ns2s(value)}`; 205 } 206 requestAnimationFrame(() => this.render()); 207 } 208 209 get startNS(): number { 210 return this._startNS; 211 } 212 213 set startNS(value: number) { 214 this._startNS = value; 215 } 216 217 get endNS(): number { 218 return this._endNS; 219 } 220 221 set endNS(value: number) { 222 this._endNS = value; 223 } 224 225 reset(): void { 226 this.loadComplete = false; 227 this.totalNS = 10_000_000_000; 228 this.startNS = 0; 229 this.endNS = 10_000_000_000; 230 if (this._rangeRuler) { 231 this._rangeRuler.drawMark = false; 232 this._rangeRuler.range.totalNS = this.totalNS; 233 this._rangeRuler.markAObj.frame.x = 0; 234 this._rangeRuler.markBObj.frame.x = this._rangeRuler.frame.width; 235 this._rangeRuler.cpuUsage = []; 236 this.sportRuler!.flagList.length = 0; 237 this.sportRuler!.slicesTimeList.length = 0; 238 this.selectionList.length = 0; 239 this.selectionMap.clear(); 240 this._rangeRuler.rangeRect = new Rect(0, 25, this.canvas?.clientWidth || 0, 75); 241 this.sportRuler!.isRangeSelect = false; 242 this.setSlicesMark(); 243 } 244 this.removeTriangle('inverted'); 245 this.setRangeNS(0, this.endNS); 246 //---------------每次导入trace时触发渲染----------------- 247 if (this._rangeRuler && this._sportRuler) { 248 this.canvas!.width = this.canvas!.clientWidth || 0; 249 sessionStorage.setItem('foldHeight', String(56.25)); 250 if (this._checkExpand && this._checkExpand === true) { 251 this._checkExpand = false; 252 sessionStorage.setItem('expand', String(this._checkExpand)); 253 } 254 sessionStorage.setItem('expand', String(this._checkExpand)); 255 this.usageEL!.innerHTML = ''; 256 this.usageEL!.style.height = `${100 - 56.25}px`; 257 this.usageEL!.style.lineHeight = `${100 - 56.25}px`; 258 this.timerShaftEL!.style.height = `${146 - 56.25 + 2}px`; 259 this.canvas!.style.height = `${146 - 56.25}px`; 260 this.canvas!.height = 146 - 56.25; 261 this.rowsPaneEL!.style.maxHeight = '100%'; 262 this._sportRuler.frame.y = 43.75; 263 264 this.render(); 265 this._checkExpand = true; 266 this._cpuUsageCount = []; //清空判断数据 267 } 268 } 269 270 initElements(): void { 271 this.root = this.shadowRoot?.querySelector('.root'); 272 this.canvas = this.shadowRoot?.querySelector('.panel'); 273 this.totalEL = this.shadowRoot?.querySelector('.total'); 274 this.collect1 = this.shadowRoot?.querySelector('#collect1'); 275 this.timeTotalEL = this.shadowRoot?.querySelector('.time-total'); 276 this.timeOffsetEL = this.shadowRoot?.querySelector('.time-offset'); 277 this.collecBtn = this.shadowRoot?.querySelector('.time-collect'); 278 this.collectGroup = this.shadowRoot?.querySelector('.collect_group'); 279 this.collectGroup?.addEventListener('click', (e) => { 280 // @ts-ignore 281 if (e.target && e.target.tagName === 'INPUT') { 282 // @ts-ignore 283 window.publish(window.SmartEvent.UI.CollectGroupChange, e.target.value); 284 } 285 }); 286 // @ts-ignore 287 procedurePool.timelineChange = (a: unknown): void => this.rangeChangeHandler?.(a); 288 // @ts-ignore 289 window.subscribe(window.SmartEvent.UI.TimeRange, (b) => this.setRangeNS(b.startNS, b.endNS)); 290 // -----------------------------点击负载区展开折叠--------------------------------- 291 this.usageEL = this.shadowRoot?.querySelector('.cpu-usage'); 292 this.timerShaftEL = this.shadowRoot!.host.parentNode?.querySelector('.timer-shaft'); 293 this.rowsPaneEL = this.shadowRoot!.host.parentNode?.querySelector('.rows-pane'); 294 this.favoriteChartListEL = this.shadowRoot!.host.parentNode?.querySelector('#favorite-chart-list'); 295 const height = this.canvas?.clientHeight || 0; 296 // 点击cpu usage部分,切换折叠展开 297 this.usageEL?.addEventListener('click', (e) => { 298 if (this._rangeRuler && this.sportRuler && this._cpuUsageCount.length) { 299 // 计算需要被收起来的高度:总高度75-(总高度/cpu数量)* 2 300 this._usageFoldHeight = this.usageExpandHeight - (this.usageExpandHeight / this._rangeRuler.cpuCountData!) * 2; 301 this.canvas!.width = this.canvas!.clientWidth || 0; 302 if (this._checkExpand) { 303 sessionStorage.setItem('expand', String(this._checkExpand)); 304 sessionStorage.setItem('foldHeight', String(this._usageFoldHeight)); 305 this.usageEL!.style.height = '100px'; 306 this.usageEL!.style.lineHeight = '100px'; 307 this.timerShaftEL!.style.height = `${height + 2}px`; 308 this.canvas!.style.height = `${height}px`; 309 this.canvas!.height = height; 310 this._rangeRuler.frame.height = 75; 311 this.sportRuler.frame.y = 100; 312 this.render(); 313 this._checkExpand = false; 314 this.favoriteChartListEL?.refreshFavoriteCanvas(); //刷新收藏泳道画布高度 315 } else { 316 sessionStorage.setItem('expand', String(this._checkExpand)); 317 sessionStorage.setItem('foldHeight', String(this._usageFoldHeight)); 318 this.usageEL!.style.height = `${100 - this._usageFoldHeight}px`; 319 this.usageEL!.style.lineHeight = `${100 - this._usageFoldHeight}px`; 320 this.timerShaftEL!.style.height = `${height - this._usageFoldHeight + 2}px`; 321 this.canvas!.style.height = `${height - this._usageFoldHeight}px`; 322 this.canvas!.height = height - this._usageFoldHeight; 323 this._rangeRuler.frame.height = 75 - this._usageFoldHeight; 324 this.sportRuler.frame.y = 100 - this._usageFoldHeight; 325 this.render(); 326 this._checkExpand = true; 327 this.favoriteChartListEL?.refreshFavoriteCanvas(); //刷新收藏泳道画布高度 328 } 329 } 330 }); 331 } 332 333 getRangeRuler(): RangeRuler | undefined { 334 return this._rangeRuler; 335 } 336 337 connectedCallback(): RangeRuler | undefined { 338 if (this.canvas) { 339 if (this.isOffScreen) { 340 // @ts-ignore 341 this.offscreen = this.canvas.transferControlToOffscreen(); 342 return; 343 } else { 344 this.ctx = this.canvas?.getContext('2d', { alpha: true }); 345 } 346 } 347 if (this.timeTotalEL) { 348 this.timeTotalEL.textContent = ns2s(this._totalNS); 349 } 350 if (this.timeOffsetEL && this._rangeRuler) { 351 this.timeOffsetEL.textContent = ns2UnitS(this._startNS, this._rangeRuler.getScale()); 352 } 353 const width = this.canvas?.clientWidth || 0; 354 const height = this.canvas?.clientHeight || 0; 355 this.setTimeRuler(width); 356 this.setSportRuler(width, height); 357 this.setRangeRuler(width); 358 } 359 360 private setTimeRuler(width: number): void { 361 if (!this.timeRuler) { 362 this.timeRuler = new TimeRuler(this, new Rect(0, 0, width, 20), this._totalNS); 363 } 364 this.timeRuler.frame.width = width; 365 } 366 367 private setSportRuler(width: number, height: number): void { 368 if (!this._sportRuler) { 369 this._sportRuler = new SportRuler( 370 this, 371 new Rect(0, 100, width, height - 100), 372 (hoverFlag, selectFlag) => { 373 this.flagChangeHandler?.(hoverFlag, selectFlag); 374 }, 375 (flag) => { 376 this.flagClickHandler?.(flag); 377 }, 378 (slicetime) => { 379 this.rangeClickHandler?.(slicetime); 380 } 381 ); 382 } 383 this._sportRuler.frame.width = width; 384 } 385 386 private setRangeRuler(width: number): void { 387 if (!this._rangeRuler) { 388 this._rangeRuler = new RangeRuler( 389 this, 390 new Rect(0, 25, width, 75 - this._usageFoldHeight), 391 { 392 slicesTime: { 393 startTime: null, 394 endTime: null, 395 color: null, 396 }, 397 scale: 0, 398 startX: 0, 399 endX: this.canvas?.clientWidth || 0, 400 startNS: 0, 401 endNS: this.totalNS, 402 totalNS: this.totalNS, 403 refresh: true, 404 xs: [], 405 xsTxt: [], 406 }, 407 (a) => { 408 if (a.startNS >= 0 && a.endNS >= 0) { 409 if (this._sportRuler) { 410 this._sportRuler.range = a; 411 } 412 if (this.timeOffsetEL && this._rangeRuler) { 413 this.timeOffsetEL.textContent = ns2UnitS(a.startNS, this._rangeRuler.getScale()); 414 } 415 if (this.loadComplete) { 416 this.rangeChangeHandler?.(a); 417 } 418 } 419 } 420 ); 421 } 422 this._rangeRuler.frame.width = width; 423 } 424 425 setRangeNS(startNS: number, endNS: number): void { 426 info('set startNS values :' + startNS + 'endNS values : ' + endNS); 427 this._rangeRuler?.setRangeNS(startNS, endNS); 428 } 429 430 getRange(): TimeRange | undefined { 431 return this._rangeRuler?.getRange(); 432 } 433 434 updateWidth(width: number): void { 435 this.dpr = window.devicePixelRatio || 1; 436 this.canvas!.width = width - (this.totalEL?.clientWidth || 0); 437 this.canvas!.height = this.shadowRoot!.host.clientHeight || 0; 438 let oldWidth = this.canvas!.width; 439 let oldHeight = this.canvas!.height; 440 this.canvas!.width = Math.ceil(oldWidth * this.dpr); 441 this.canvas!.height = Math.ceil(oldHeight * this.dpr); 442 this.canvas!.style.width = oldWidth + 'px'; 443 this.canvas!.style.height = oldHeight + 'px'; 444 this.ctx?.scale(this.dpr, this.dpr); 445 this.ctx?.translate(0, 0); 446 this._rangeRuler!.frame.width = oldWidth; 447 this._sportRuler!.frame.width = oldWidth; 448 this.timeRuler!.frame.width = oldWidth; 449 this._rangeRuler?.fillX(); 450 this.render(); 451 } 452 453 documentOnMouseDown = (ev: MouseEvent): void => { 454 // @ts-ignore 455 if ((window as unknown).isSheetMove) { 456 return; 457 } 458 this._rangeRuler?.mouseDown(ev); 459 }; 460 461 documentOnMouseUp = (ev: MouseEvent): void => { 462 // @ts-ignore 463 if ((window as unknown).isSheetMove) { 464 return; 465 } 466 this._rangeRuler?.mouseUp(ev); 467 this.sportRuler?.mouseUp(ev); 468 }; 469 470 documentOnMouseMove = (ev: MouseEvent, trace: SpSystemTrace): void => { 471 trace.style.cursor = 'default'; 472 let x = ev.offsetX - (this.canvas?.offsetLeft || 0); // 鼠标的x轴坐标 473 let y = ev.offsetY; // 鼠标的y轴坐标 474 let findSlicestime = this.sportRuler?.findSlicesTime(x, y); // 查找帽子 475 if (!findSlicestime) { 476 // 如果在该位置没有找到一个“帽子”,则可以显示一个旗子。 477 this.sportRuler?.showHoverFlag(); 478 this._rangeRuler?.mouseMove(ev, trace); 479 if (this.sportRuler?.edgeDetection(ev)) { 480 this.sportRuler?.mouseMove(ev); 481 } else { 482 this.sportRuler?.mouseOut(ev); 483 } 484 } else { 485 this.sportRuler?.clearHoverFlag(); 486 this.sportRuler?.modifyFlagList(null); //重新绘制旗子,清除hover flag 487 } 488 }; 489 490 documentOnMouseOut = (ev: MouseEvent): void => { 491 this._rangeRuler?.mouseOut(ev); 492 this.sportRuler?.mouseOut(ev); 493 }; 494 495 documentOnKeyPress = (ev: KeyboardEvent, currentSlicesTime?: CurrentSlicesTime): void => { 496 // @ts-ignore 497 if ((window as unknown).flagInputFocus) { 498 return; 499 } 500 this._rangeRuler?.keyPress(ev, currentSlicesTime); 501 this.sportRuler?.clearHoverFlag(); 502 }; 503 504 documentOnKeyUp = (ev: KeyboardEvent): void => { 505 // @ts-ignore 506 if ((window as unknown).flagInputFocus) { 507 return; 508 } 509 this._rangeRuler?.keyUp(ev); 510 }; 511 512 disconnectedCallback(): void { } 513 514 firstRender = true; 515 516 lineColor(): string { 517 return window.getComputedStyle(this.canvas!, null).getPropertyValue('color'); 518 } 519 520 render(): void { 521 this.dpr = window.devicePixelRatio || 1; 522 if (this.ctx) { 523 this.ctx.fillStyle = 'transparent'; 524 this.ctx?.fillRect(0, 0, this.canvas?.width || 0, this.canvas?.height || 0); 525 this.timeRuler?.draw(); 526 this._rangeRuler?.draw(); 527 this._sportRuler?.draw(); 528 } else { 529 procedurePool.submitWithName( 530 'timeline', 531 'timeline', 532 { 533 offscreen: this.must ? this.offscreen : undefined, //是否离屏 534 dpr: this.dpr, //屏幕dpr值 535 hoverX: this.hoverX, 536 hoverY: this.hoverY, 537 canvasWidth: this.canvasWidth, 538 canvasHeight: this.canvasHeight, 539 keyPressCode: null, 540 keyUpCode: null, 541 lineColor: '#dadada', 542 startNS: this.startNS, 543 endNS: this.endNS, 544 totalNS: this.totalNS, 545 frame: this.frame, 546 }, 547 this.must ? this.offscreen : undefined, 548 (res: unknown) => { 549 this.must = false; 550 } 551 ); 552 } 553 } 554 555 modifyFlagList(flag: Flag | null | undefined): void { 556 this._sportRuler?.modifyFlagList(flag); 557 } 558 559 modifySlicesList(slicestime: SlicesTime | null | undefined): void { 560 this._sportRuler?.modifySicesTimeList(slicestime); 561 } 562 cancelPressFrame(): void { 563 this._rangeRuler?.cancelPressFrame(); 564 } 565 566 cancelUpFrame(): void { 567 this._rangeRuler?.cancelUpFrame(); 568 } 569 570 stopWASD(ev: unknown): void { 571 // @ts-ignore 572 this._rangeRuler?.keyUp(ev); 573 } 574 575 drawTriangle(time: number, type: string): unknown { 576 return this._sportRuler?.drawTriangle(time, type); 577 } 578 579 removeTriangle(type: string): void { 580 this._sportRuler?.removeTriangle(type); 581 } 582 583 setSlicesMark( 584 startTime: null | number = null, 585 endTime: null | number = null, 586 shiftKey: null | boolean = false 587 ): SlicesTime | null | undefined { 588 let sliceTime = this._sportRuler?.setSlicesMark(startTime, endTime, shiftKey); 589 if (sliceTime && sliceTime !== undefined) { 590 this.traceSheetEL?.displayCurrent(sliceTime); // 给当前pane准备数据 591 592 // 取最新创建的那个selection对象 593 let selection = this.selectionList[this.selectionList.length - 1]; 594 if (selection) { 595 selection.isCurrentPane = true; // 设置当前面板为可以显示的状态 596 //把刚刚创建的slicetime和selection对象关联起来,以便后面再次选中“跑道”的时候显示对应的面板。 597 this.selectionMap.set(sliceTime.id, selection); 598 this.traceSheetEL?.rangeSelect(selection); // 显示选中区域对应的面板 599 } 600 } 601 return sliceTime; 602 } 603 604 displayCollect(showCollect: boolean): void { 605 if (showCollect) { 606 this.collecBtn!.style.display = 'flex'; 607 } else { 608 this.collecBtn!.style.display = 'none'; 609 } 610 } 611 612 initHtml(): string { 613 return TimerShaftElementHtml; 614 } 615} 616