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 { TraceRow } from './base/TraceRow'; 18import { dpr } from './base/Extension'; 19import { 20 drawFlagLineSegment, 21 drawLines, 22 drawLinkLines, 23 drawLogsLineSegment, 24 drawWakeUp, 25 drawWakeUpList, 26 PairPoint, 27 Rect, 28} from '../../database/ui-worker/ProcedureWorkerCommon'; 29import { Flag } from './timer-shaft/Flag'; 30import { TimerShaftElement } from './TimerShaftElement'; 31import { CpuStruct } from '../../database/ui-worker/cpu/ProcedureWorkerCPU'; 32import { WakeupBean } from '../../bean/WakeupBean'; 33import { LitIcon } from '../../../base-ui/icon/LitIcon'; 34 35const maxScale = 0.8; //收藏最大高度为界面最大高度的80% 36const topHeight = 150; // 顶部cpu使用率部分高度固定为150px 37const minHeight = 40; //泳道最低高度为40 38const mouseMoveRange = 5; 39 40@element('sp-chart-list') 41export class SpChartList extends BaseElement { 42 private static COLLECT_G1 = '1'; 43 private static COLLECT_G2 = '2'; 44 private collectEl1: HTMLDivElement | null | undefined; 45 private collectEl2: HTMLDivElement | null | undefined; 46 private groupTitle1: HTMLDivElement | null | undefined; 47 private groupTitle2: HTMLDivElement | null | undefined; 48 private icon1: LitIcon | null | undefined; 49 private icon2: LitIcon | null | undefined; 50 private removeCollectIcon1: LitIcon | null | undefined; 51 private removeCollectIcon2: LitIcon | null | undefined; 52 private rootEl: HTMLDivElement | null | undefined; 53 private fragmentGroup1: DocumentFragment = document.createDocumentFragment(); 54 private fragmentGroup2: DocumentFragment = document.createDocumentFragment(); 55 private canvas: HTMLCanvasElement | null | undefined; //绘制收藏泳道图 56 private canvasCtx: CanvasRenderingContext2D | undefined | null; 57 private canResize: boolean = false; 58 private isPress: boolean = false; 59 private startPageY = 0; 60 private startClientHeight: number = 0; 61 private scrollTimer: unknown; 62 private collect1Expand: boolean = true; 63 private collect2Expand: boolean = true; 64 // @ts-ignore 65 private collectRowList1: Array<TraceRow<unknown>> = []; 66 // @ts-ignore 67 private collectRowList2: Array<TraceRow<unknown>> = []; 68 private maxHeight = 0; 69 private manualHeight = 0; 70 71 initElements(): void { 72 this.collectEl1 = this.shadowRoot?.querySelector<HTMLDivElement>('#collect-group-1'); 73 this.collectEl2 = this.shadowRoot?.querySelector<HTMLDivElement>('#collect-group-2'); 74 this.groupTitle1 = this.shadowRoot?.querySelector<HTMLDivElement>('#group-1-title'); 75 this.groupTitle2 = this.shadowRoot?.querySelector<HTMLDivElement>('#group-2-title'); 76 this.icon1 = this.shadowRoot?.querySelector<LitIcon>('#group_1_expand'); 77 this.icon2 = this.shadowRoot?.querySelector<LitIcon>('#group_2_expand'); 78 this.removeCollectIcon1 = this.shadowRoot?.querySelector<LitIcon>('#group_1_collect'); 79 this.removeCollectIcon2 = this.shadowRoot?.querySelector<LitIcon>('#group_2_collect'); 80 this.rootEl = this.shadowRoot?.querySelector<HTMLDivElement>('.root'); 81 this.canvas = this.shadowRoot?.querySelector<HTMLCanvasElement>('.panel-canvas'); 82 this.canvasCtx = this.canvas?.getContext('2d'); //@ts-ignore 83 window.subscribe(window.SmartEvent.UI.RowHeightChange, (data: { expand: number; value: number }) => { 84 this.resizeHeight(); 85 if (!data.expand) { 86 let offset = this.scrollTop - data.value; 87 offset = offset < 0 ? 0 : offset; 88 this.scrollTop = offset; 89 } 90 this.refreshFavoriteCanvas(); 91 }); 92 this.initChartListListener(); 93 } 94 95 private initChartListListener(): void { 96 const foldCollect1 = (): void => { 97 this.collect1Expand = !this.collect1Expand; 98 if (this.collect1Expand) { 99 this.icon1!.style.transform = 'rotateZ(0deg)'; 100 this.collectEl1?.appendChild(this.fragmentGroup1); 101 } else { 102 this.icon1!.style.transform = 'rotateZ(-90deg)'; 103 this.collectRowList1.forEach((row) => this.fragmentGroup1.appendChild(row)); 104 } 105 this.resizeHeight(); 106 }; 107 this.icon1?.addEventListener('click', () => foldCollect1()); 108 const foldCollect2 = (): void => { 109 this.collect2Expand = !this.collect2Expand; 110 if (this.collect2Expand) { 111 this.icon2!.style.transform = 'rotateZ(0deg)'; 112 this.collectEl2?.appendChild(this.fragmentGroup2); 113 this.scrollTop = this.scrollHeight; 114 } else { 115 this.icon2!.style.transform = 'rotateZ(-90deg)'; 116 this.collectRowList2.forEach((row) => this.fragmentGroup2.appendChild(row)); 117 this.scrollTop = 0; 118 } 119 this.resizeHeight(); 120 }; 121 this.icon2?.addEventListener('click', () => foldCollect2()); 122 document.addEventListener('keyup', (e) => { 123 if (e.key.toLowerCase() === 'b' && e.ctrlKey === false) { 124 // 收藏夹有泳道时 为true 125 const hasChildNode1 = this.collectEl1?.hasChildNodes() || this.fragmentGroup1.hasChildNodes(); 126 const hasChildNode2 = this.collectEl2?.hasChildNodes() || this.fragmentGroup2.hasChildNodes(); 127 // 两个收藏夹都有泳道时 128 if (hasChildNode1 && hasChildNode2) { 129 const flag = this.collect1Expand === this.collect2Expand; 130 if (flag) { 131 foldCollect1(); 132 foldCollect2(); 133 } else { 134 // 两收藏夹的折叠状态不一致 优先一起折叠 135 if (this.collect1Expand) { 136 foldCollect1(); 137 } 138 else { 139 foldCollect2(); 140 } 141 } 142 return; 143 } 144 // 只影响有泳道的收藏夹 145 if (hasChildNode1) { 146 foldCollect1(); 147 } 148 if (hasChildNode2) { 149 foldCollect2(); 150 } 151 } 152 }); 153 154 this.removeCollectIcon1?.addEventListener('click', () => { 155 Array.from(this.collectRowList1).forEach(row => { 156 row.collectEL?.click(); 157 }); 158 }); 159 this.removeCollectIcon2?.addEventListener('click', () => { 160 Array.from(this.collectRowList2).forEach(row => { 161 row.collectEL?.click(); 162 }); 163 }); 164 } 165 166 removeAllCollectRow(): void { 167 Array.from(this.collectRowList1).forEach(row => { 168 row.collectEL?.click(); 169 }); 170 Array.from(this.collectRowList2).forEach(row => { 171 row.collectEL?.click(); 172 }); 173 } 174 175 private resizeHeight(): void { 176 this.maxHeight = 0; 177 // @ts-ignore 178 this.collectEl1!.childNodes.forEach((item) => (this.maxHeight += (item as unknown).clientHeight)); 179 // @ts-ignore 180 this.collectEl2!.childNodes.forEach((item) => (this.maxHeight += (item as unknown).clientHeight)); 181 if (this.groupTitle1) { 182 this.maxHeight += this.groupTitle1.clientHeight; 183 } 184 if (this.groupTitle2) { 185 this.maxHeight += this.groupTitle2.clientHeight; 186 } 187 188 this.maxHeight = Math.min(this.getMaxLimitHeight(), this.maxHeight); 189 if (this.manualHeight > 0) { 190 this.style.height = `${Math.min(this.maxHeight, this.manualHeight)}px`; 191 } else { 192 this.style.height = `${this.maxHeight}px`; 193 } 194 } 195 196 private getMaxLimitHeight(): number { 197 return (this.parentElement!.clientHeight - topHeight) * maxScale; 198 } 199 200 // @ts-ignore 201 getCollectRows(filter?: (row: TraceRow<unknown>) => boolean): Array<TraceRow<unknown>> | [] { 202 if (filter) { 203 return [...this.collectRowList1.filter(filter), ...this.collectRowList2.filter(filter)]; 204 } else { 205 return this.getAllCollectRows(); 206 } 207 } 208 209 getRowScrollTop(): number { 210 return this.rootEl?.scrollTop || 0; 211 } 212 213 // @ts-ignore 214 expandSearchRowGroup(row: TraceRow<unknown>): void { 215 this.updateGroupDisplay(); 216 if (row.collectGroup === SpChartList.COLLECT_G1) { 217 if (!this.collect1Expand) { 218 this.collect1Expand = true; 219 this.icon1!.style.transform = 'rotateZ(0deg)'; 220 this.collectEl1?.appendChild(this.fragmentGroup1); 221 } 222 } else { 223 if (!this.collect2Expand) { 224 this.collect2Expand = true; 225 this.icon2!.style.transform = 'rotateZ(0deg)'; 226 this.collectEl2?.appendChild(this.fragmentGroup2); 227 this.scrollTop = this.scrollHeight; 228 } 229 } 230 this.resizeHeight(); 231 } 232 233 // @ts-ignore 234 getCollectRow(filter: (row: TraceRow<unknown>) => boolean): TraceRow<unknown> | undefined { 235 return this.collectRowList1.find(filter) || this.collectRowList2.find(filter); 236 } 237 238 // @ts-ignore 239 getAllCollectRows(): Array<TraceRow<unknown>> { 240 return [...this.collectRowList1, ...this.collectRowList2]; 241 } 242 243 getCollectRowsInfo(group: string): unknown { 244 return (group === SpChartList.COLLECT_G1 ? this.collectRowList1 : this.collectRowList2).map((row) => { 245 let rowJson = { 246 type: row.rowType, 247 name: row.name, 248 id: row.rowId, 249 parents: [], 250 }; 251 this.getRowParent(rowJson, row); 252 rowJson.parents.reverse(); 253 return rowJson; 254 }); 255 } 256 257 // @ts-ignore 258 getRowParent(obj: unknown, row: TraceRow<unknown>): void { 259 if (row.parentRowEl) { 260 // @ts-ignore 261 if (obj.parents) { 262 let parent: unknown = { 263 type: row.parentRowEl.rowType, 264 name: row.parentRowEl.name, 265 id: row.parentRowEl.rowId, 266 }; 267 // @ts-ignore 268 (obj.parents as Array<unknown>).push(parent); 269 } else { 270 // @ts-ignore 271 obj.parents = [parent]; 272 } 273 this.getRowParent(obj, row.parentRowEl); 274 } 275 } 276 277 // @ts-ignore 278 getAllSelectCollectRows(): Array<TraceRow<unknown>> { 279 // @ts-ignore 280 const rows: Array<TraceRow<unknown>> = []; 281 for (const row of this.collectRowList1) { 282 if (row.checkType === '2') { 283 rows.push(row); 284 } 285 } 286 for (const row of this.collectRowList2) { 287 if (row.checkType === '2') { 288 rows.push(row); 289 } 290 } 291 return rows; 292 } 293 294 insertRowBefore(node: Node, child: Node): void { 295 // @ts-ignore 296 if (child === null || (child as TraceRow<unknown>).collectGroup === (node as TraceRow<unknown>).collectGroup) { 297 // @ts-ignore 298 if ((node as TraceRow<unknown>).collectGroup === SpChartList.COLLECT_G1) { 299 this.collectEl1!.insertBefore(node, child); 300 // @ts-ignore 301 this.collectRowList1 = Array.from(this.collectEl1!.children) as TraceRow<unknown>[]; 302 } else { 303 this.collectEl2!.insertBefore(node, child); 304 // @ts-ignore 305 this.collectRowList2 = Array.from(this.collectEl2!.children) as TraceRow<unknown>[]; 306 } 307 } 308 } 309 310 reset(): void { 311 this.maxHeight = 0; 312 this.clearRect(); 313 this.collect1Expand = true; 314 this.collect2Expand = true; 315 this.icon1!.style.transform = 'rotateZ(0deg)'; 316 this.icon2!.style.transform = 'rotateZ(0deg)'; 317 this.collectRowList1.forEach((row) => { 318 row.clearMemory(); 319 }); 320 this.collectRowList2.forEach((row) => { 321 row.clearMemory(); 322 }); 323 this.collectRowList1 = []; 324 this.collectRowList2 = []; 325 this.fragmentGroup1 = document.createDocumentFragment(); 326 this.fragmentGroup2 = document.createDocumentFragment(); 327 this.collectEl1!.innerHTML = ''; 328 this.collectEl2!.innerHTML = ''; 329 this.updateGroupDisplay(); 330 this.style.height = 'auto'; 331 } 332 333 context(): CanvasRenderingContext2D | undefined | null { 334 return this.canvasCtx; 335 } 336 337 getCanvas(): HTMLCanvasElement | null | undefined { 338 return this.canvas; 339 } 340 341 connectedCallback(): void { 342 super.connectedCallback(); 343 const vessel = this.parentNode as HTMLDivElement; 344 vessel.addEventListener('mousedown', this.onMouseDown); 345 vessel.addEventListener('mouseup', this.onMouseUp); 346 vessel.addEventListener('mousemove', this.onMouseMove); 347 this.addEventListener('scroll', this.onScroll, { passive: true }); 348 } 349 350 disconnectedCallback(): void { 351 super.disconnectedCallback(); 352 const vessel = this.parentNode as HTMLDivElement; 353 vessel.removeEventListener('mousedown', this.onMouseDown); 354 vessel.removeEventListener('mouseup', this.onMouseUp); 355 vessel.removeEventListener('mousemove', this.onMouseMove); 356 this.removeEventListener('scroll', this.onScroll); 357 } 358 359 onScroll = (ev: Event): void => { 360 this.canvas!.style.transform = `translateY(${this.scrollTop}px)`; 361 if (this.scrollTimer) { 362 // @ts-ignore 363 clearTimeout(this.scrollTimer); 364 } 365 this.scrollTimer = setTimeout(() => { 366 TraceRow.range!.refresh = true; 367 window.publish(window.SmartEvent.UI.RefreshCanvas, {}); 368 }, 100); 369 window.publish(window.SmartEvent.UI.RefreshCanvas, {}); 370 }; 371 372 onMouseDown = (ev: MouseEvent): void => { 373 this.isPress = true; 374 this.startPageY = ev.pageY; 375 this.startClientHeight = this.clientHeight; 376 if (this.containPoint(ev)) { 377 if ( 378 this.getBoundingClientRect().bottom > ev.pageY - mouseMoveRange && 379 this.getBoundingClientRect().bottom < ev.pageY + mouseMoveRange 380 ) { 381 this.style.cursor = 'row-resize'; 382 this.canResize = true; 383 } else { 384 this.style.cursor = 'default'; 385 this.canResize = false; 386 } 387 // @ts-ignore 388 (window as unknown).collectResize = this.canResize; 389 } 390 }; 391 392 onMouseMove = (ev: MouseEvent): void => { 393 if (this.containPoint(ev)) { 394 let inResizeArea = 395 this.getBoundingClientRect().bottom > ev.pageY - mouseMoveRange && 396 this.getBoundingClientRect().bottom < ev.pageY + mouseMoveRange; 397 if ((this.isPress && this.canResize) || inResizeArea) { 398 this.style.cursor = 'row-resize'; 399 } else { 400 this.style.cursor = 'default'; 401 } 402 } 403 //防止点击触发move时间 404 if (Math.abs(ev.pageY - this.startPageY) < 2) { 405 return; 406 } 407 if (this.canResize && this.isPress) { 408 // @ts-ignore 409 (window as unknown).collectResize = true; 410 // 拖动超过所有泳道最大高度 或小于一个泳道的高度,不支持拖动 411 let newHeight = this.startClientHeight + ev.pageY - this.startPageY; 412 if (newHeight > this.maxHeight) { 413 newHeight = this.maxHeight; 414 } 415 if (newHeight > this.getMaxLimitHeight()) { 416 newHeight = this.getMaxLimitHeight(); 417 } 418 if (newHeight < minHeight) { 419 newHeight = minHeight; 420 } 421 this!.style.height = `${newHeight}px`; 422 this.manualHeight = newHeight; 423 } else { 424 // @ts-ignore 425 (window as unknown).collectResize = false; 426 } 427 }; 428 429 onMouseUp = (ev: MouseEvent): void => { 430 this.isPress = false; 431 this.canResize = false; 432 this.style.cursor = 'default'; 433 // @ts-ignore 434 (window as unknown).collectResize = false; 435 if (this.style.display === 'flex') { 436 this.refreshFavoriteCanvas(); 437 } 438 }; 439 440 // @ts-ignore 441 insertRow(row: TraceRow<unknown>, group: string, updateGroup: boolean): void { 442 this.style.display = 'flex'; 443 let collectGroup = !updateGroup && row.collectGroup ? row.collectGroup : group; 444 if (row.collectGroup !== SpChartList.COLLECT_G1 && row.collectGroup !== SpChartList.COLLECT_G2) { 445 row.collectGroup = group; 446 } 447 if (updateGroup) { 448 row.collectGroup = group; 449 } 450 if (collectGroup === SpChartList.COLLECT_G1) { 451 if (!this.collect1Expand) { 452 this.collect1Expand = true; 453 this.icon1!.style.transform = 'rotateZ(0deg)'; 454 } 455 if (this.collectRowList1.indexOf(row) === -1) { 456 this.collectRowList1.push(row); 457 } 458 if (!this.fragmentGroup1.contains(row)) { 459 this.fragmentGroup1.appendChild(row); 460 } 461 this.collectEl1?.appendChild(this.fragmentGroup1); 462 this.scrollTo({ top: this.collectEl1?.clientHeight }); 463 } else { 464 if (!this.collect2Expand) { 465 this.collect2Expand = true; 466 this.icon2!.style.transform = 'rotateZ(0deg)'; 467 } 468 if (this.collectRowList2.indexOf(row) === -1) { 469 this.collectRowList2.push(row); 470 } 471 if (!this.fragmentGroup2.contains(row)) { 472 this.fragmentGroup2.appendChild(row); 473 } 474 this.collectEl2!.appendChild(this.fragmentGroup2); 475 this.scrollTo({ top: this.scrollHeight }); 476 } 477 this.updateGroupDisplay(); 478 this.resizeHeight(); 479 this.refreshFavoriteCanvas(); 480 row.currentContext = this.canvasCtx; 481 } 482 483 // @ts-ignore 484 deleteRow(row: TraceRow<unknown>, clearCollectGroup: boolean): void { 485 if (row.collectGroup === SpChartList.COLLECT_G1) { 486 this.collectRowList1.splice(this.collectRowList1.indexOf(row), 1); 487 if (!this.fragmentGroup1.contains(row)) { 488 this.fragmentGroup1.appendChild(row); 489 } 490 this.fragmentGroup1.removeChild(row); 491 } else { 492 this.collectRowList2.splice(this.collectRowList2.indexOf(row), 1); 493 if (!this.fragmentGroup2.contains(row)) { 494 this.fragmentGroup2.appendChild(row); 495 } 496 this.fragmentGroup2.removeChild(row); 497 } 498 if (clearCollectGroup) { 499 row.collectGroup = undefined; 500 } 501 this.updateGroupDisplay(); 502 this.resizeHeight(); 503 this.scrollTop = 0; 504 this.refreshFavoriteCanvas(); 505 row.currentContext = undefined; 506 if (this.collectRowList1.length === 0 && this.collectRowList2.length === 0) { 507 this.style.height = 'auto'; 508 this.style.display = 'none'; 509 this.manualHeight = 0; 510 } 511 } 512 513 hideCollectArea(): void { 514 if (this.collect1Expand) { 515 this.collectRowList1.forEach((row) => this.fragmentGroup1.appendChild(row)); 516 } 517 if (this.collect2Expand) { 518 this.collectRowList2.forEach((row) => this.fragmentGroup2.appendChild(row)); 519 } 520 this.groupTitle1!.style.display = 'none'; 521 this.groupTitle2!.style.display = 'none'; 522 this.resizeHeight(); 523 } 524 525 showCollectArea(): void { 526 if (this.collect1Expand) { 527 this.collectEl1?.appendChild(this.fragmentGroup1); 528 } 529 if (this.collect2Expand) { 530 this.collectEl2?.appendChild(this.fragmentGroup2); 531 } 532 this.updateGroupDisplay(); 533 this.resizeHeight(); 534 } 535 536 updateGroupDisplay(): void { 537 this.groupTitle1!.style.display = this.collectRowList1.length === 0 ? 'none' : 'flex'; 538 this.groupTitle2!.style.display = this.collectRowList2.length === 0 ? 'none' : 'flex'; 539 } 540 541 hasCollectRow(): boolean { 542 return this.collectRowList2.length > 0 || this.collectRowList1.length > 0; 543 } 544 545 clearRect(): void { 546 this.canvasCtx?.clearRect(0, 0, this.canvas?.clientWidth ?? 0, this.canvas?.clientHeight ?? 0); 547 } 548 549 drawLines(xs: number[] | undefined, color: string): void { 550 drawLines(this.canvasCtx!, xs ?? [], this.clientHeight, color); 551 } 552 553 drawFlagLineSegment( 554 hoverFlag: Flag | undefined | null, 555 selectFlag: Flag | undefined | null, 556 tse: TimerShaftElement 557 ): void { 558 drawFlagLineSegment( 559 this.canvasCtx, 560 hoverFlag, 561 selectFlag, 562 new Rect(0, 0, TraceRow.FRAME_WIDTH, this.canvas?.clientHeight!), 563 tse 564 ); 565 } 566 567 drawWakeUp(): void { 568 drawWakeUp( 569 this.canvasCtx, 570 CpuStruct.wakeupBean, 571 TraceRow.range!.startNS, 572 TraceRow.range!.endNS, 573 TraceRow.range!.totalNS, 574 new Rect(0, 0, TraceRow.FRAME_WIDTH, this.canvas?.clientHeight!) 575 ); 576 } 577 578 drawWakeUpList(bean: WakeupBean): void { 579 drawWakeUpList(this.canvasCtx, bean, TraceRow.range!.startNS, TraceRow.range!.endNS, TraceRow.range!.totalNS, { 580 x: 0, 581 y: 0, 582 width: TraceRow.FRAME_WIDTH, 583 height: this.canvas!.clientHeight!, 584 } as Rect); 585 } 586 587 drawLogsLineSegment(bean: Flag | null | undefined, timeShaft: TimerShaftElement): void { 588 drawLogsLineSegment( 589 this.canvasCtx, 590 bean, 591 { 592 x: 0, 593 y: 0, 594 width: TraceRow.FRAME_WIDTH, 595 height: this.canvas!.clientHeight, 596 }, 597 timeShaft 598 ); 599 } 600 601 drawLinkLines(nodes: PairPoint[][], tse: TimerShaftElement, isFavorite: boolean, favoriteHeight: number): void { 602 drawLinkLines(this.canvasCtx!, nodes, tse, isFavorite, favoriteHeight); 603 } 604 605 refreshFavoriteCanvas(): void { 606 this.canvas!.style.width = `${this.clientWidth - 248}px`; 607 this.canvas!.style.left = `248px`; 608 this.canvas!.width = this.canvas?.clientWidth! * dpr(); 609 this.canvas!.height = this.clientHeight * dpr(); 610 this.canvas!.getContext('2d')!.scale(dpr(), dpr()); 611 window.publish(window.SmartEvent.UI.RefreshCanvas, {}); 612 } 613 614 private getHtmlCss(): string { 615 return `<style> 616 :host{ 617 display: none; 618 width: 100%; 619 height: auto; 620 overflow-anchor: none; 621 z-index: 3; 622 box-shadow: 0 10px 10px #00000044; 623 position: relative; 624 overflow: auto; 625 overflow-x: hidden; 626 scroll-behavior: smooth; 627 } 628 .root{ 629 width: 100%; 630 box-sizing: border-box; 631 } 632 .panel-canvas{ 633 position: absolute; 634 top: 0; 635 right: 0; 636 bottom: 0; 637 box-sizing: border-box; 638 } 639 .icon:hover { 640 color:#ecb93f; 641 } 642 .icon { 643 margin-right: 10px; 644 cursor: pointer; 645 } 646 </style>`; 647 } 648 649 initHtml(): string { 650 return ` 651 ${this.getHtmlCss()} 652<canvas id="canvas-panel" class="panel-canvas" ondragstart="return false"></canvas> 653<div class="root"> 654 <div id="group-1-title" style="background-color: #efefef;padding: 10px;align-items: center"> 655 <lit-icon id="group_1_expand" class="icon" name="caret-down" size="19"></lit-icon> 656 <span style="width: 184px;font-size: 10px;color: #898989">G1</span> 657 <lit-icon id="group_1_collect" name="star-fill" style="color: #5291FF;cursor: pointer" size="19"></lit-icon> 658 </div> 659 <div id="collect-group-1"></div> 660 <div id="group-2-title" style="background-color: #efefef;padding: 10px;align-items: center"> 661 <lit-icon id="group_2_expand" class="icon" name="caret-down" size="19"></lit-icon> 662 <span style="width: 184px;font-size: 10px;color: #898989">G2</span> 663 <lit-icon id="group_2_collect" name="star-fill" style="color: #f56940;cursor: pointer" size="19"></lit-icon> 664 </div> 665 <div id="collect-group-2"></div> 666</div> 667`; 668 } 669} 670