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