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 { LitTableColumn } from './lit-table-column'; 17import { LitProgressBar } from './../progress-bar/LitProgressBar'; 18import { element } from '../BaseElement'; 19import '../utils/Template'; 20import { TableRowObject } from './TableRowObject'; 21import { ExcelFormater } from '../utils/ExcelFormater'; 22import { LitIcon } from '../icon/LitIcon'; 23import { NodeType } from '../../js-heap/model/DatabaseStruct'; 24import { ConstructorType } from '../../js-heap/model/UiStruct'; 25import { JsCpuProfilerStatisticsStruct } from '../../trace/bean/JsStruct'; 26import { 27 iconPadding, 28 iconWidth, 29 createDownUpSvg, 30 litTableHtml, 31 exportData, 32 formatExportData, 33 recursionExportTableData, 34 addCopyEventListener, 35 addSelectAllBox, 36 fixed, 37 formatName 38} from './LitTableHtml'; 39 40@element('lit-table') 41export class LitTable extends HTMLElement { 42 meauseRowElement: HTMLDivElement | undefined; 43 currentRecycleList: HTMLDivElement[] = []; 44 currentTreeDivList: HTMLDivElement[] = []; 45 public rememberScrollTop = false; 46 public getItemTextColor?: (data: any) => string; 47 public itemTextHandleMap: Map<string, (value: any) => string> = new Map<string, (value: any) => string>(); 48 public exportTextHandleMap: Map<string, (value: any) => string> = new Map<string, (value: any) => string>(); 49 public ds: Array<any> = []; 50 public recycleDs: Array<any> = []; 51 public gridTemplateColumns: Array<string> = []; 52 public tableColumns: NodeListOf<LitTableColumn> | undefined; 53 public treeElement: HTMLDivElement | undefined | null; 54 public columns: Array<Element> | null | undefined; 55 public exportLoading: boolean = false; 56 public exportProgress: LitProgressBar | null | undefined; 57 public tableElement: HTMLDivElement | null | undefined; 58 private normalDs: Array<any> = []; 59 /*Grid css layout descriptions are obtained according to the clustern[] nested structure*/ 60 private st: HTMLSlotElement | null | undefined; 61 private theadElement: HTMLDivElement | null | undefined; 62 private tbodyElement: HTMLDivElement | undefined | null; 63 private colCount: number = 0; 64 private isRecycleList: boolean = true; 65 private isScrollXOutSide: boolean = false; 66 private value: Array<any> = []; 67 private _mode = TableMode.Expand; 68 private columnResizeEnable: boolean = true; 69 private _isSearch: boolean = false; 70 71 constructor() { 72 super(); 73 const shadowRoot = this.attachShadow({ mode: 'open' }); 74 shadowRoot.innerHTML = litTableHtml; 75 } 76 77 static get observedAttributes(): string[] { 78 return [ 79 'scroll-y', 80 'selectable', 81 'no-head', 82 'grid-line', 83 'defaultOrderColumn', 84 'hideDownload', 85 'noRecycle', 86 'loading', 87 'expand', 88 ]; 89 } 90 91 set mode(mode: TableMode) { 92 this._mode = mode; 93 } 94 95 set loading(value: boolean) { 96 this.exportProgress!.loading = value; 97 } 98 99 get hideDownload(): boolean { 100 return this.hasAttribute('hideDownload'); 101 } 102 103 set hideDownload(value) { 104 if (value) { 105 this.setAttribute('hideDownload', ''); 106 } else { 107 this.removeAttribute('hideDownload'); 108 } 109 } 110 111 get selectable(): boolean { 112 return this.hasAttribute('selectable'); 113 } 114 115 set selectable(value) { 116 if (value) { 117 this.setAttribute('selectable', ''); 118 } else { 119 this.removeAttribute('selectable'); 120 } 121 } 122 123 get scrollY(): string { 124 return this.getAttribute('scroll-y') || 'auto'; 125 } 126 127 set scrollY(value) { 128 this.setAttribute('scroll-y', value); 129 } 130 131 get dataSource(): any[] { 132 return this.ds || []; 133 } 134 135 set dataSource(value) { 136 if (this.hasAttribute('noRecycle')) { 137 this.ds = value; 138 this.isRecycleList = false; 139 this.renderTable(); 140 } else { 141 this.columnResizeEnable = false; 142 this.recycleDataSource = value; 143 } 144 } 145 146 set noRecycle(value) { 147 if (value) { 148 this.setAttribute('noRecycle', ''); 149 } else { 150 this.removeAttribute('noRecycle'); 151 } 152 } 153 154 get noRecycle(): boolean { 155 return this.hasAttribute('noRecycle'); 156 } 157 158 get recycleDataSource(): any[] { 159 return this.ds || []; 160 } 161 162 set isSearch(value: boolean) { 163 this._isSearch = value; 164 } 165 166 set recycleDataSource(value) { 167 if (this.tableElement) { 168 this.isScrollXOutSide = this.tableElement!.scrollWidth > this.tableElement!.clientWidth; 169 this.isRecycleList = true; 170 this.ds = value; 171 if (this.rememberScrollTop) { 172 this.tableElement!.scrollTop = 0; 173 this.tableElement!.scrollLeft = 0; 174 } else { 175 this.tableElement!.scrollTop = 0; 176 this.tableElement!.scrollLeft = 0; 177 } 178 if (this.hasAttribute('tree')) { 179 if (value.length === 0) { 180 this.value = []; 181 this.recycleDs = this.meauseTreeRowElement(value); 182 } else { 183 if ( 184 value !== this.value && 185 this.value.length !== 0 && 186 !this._isSearch && 187 this.querySelector('lit-table-column')?.hasAttribute('retract') 188 ) { 189 this.shadowRoot!.querySelector<LitIcon>('.top')!.name = 'up'; 190 this.shadowRoot!.querySelector<LitIcon>('.bottom')!.name = 'down'; 191 } 192 this._isSearch = false; 193 this.value = value; 194 this.recycleDs = this.meauseTreeRowElement(value, RedrawTreeForm.Retract); 195 } 196 } else { 197 this.recycleDs = this.meauseAllRowHeight(value); 198 } 199 } 200 } 201 202 get snapshotDataSource(): any[] { 203 return this.ds || []; 204 } 205 206 set snapshotDataSource(value) { 207 this.ds = value; 208 if (this.hasAttribute('tree')) { 209 this.recycleDs = this.meauseTreeRowElement(value, RedrawTreeForm.Default); 210 } else { 211 this.recycleDs = this.meauseAllRowHeight(value); 212 } 213 } 214 215 move1px(): void { 216 this.tableElement!.scrollTop = this.tableElement!.scrollTop + 1; 217 } 218 219 dataExportInit(): void { 220 let exportDiv = this.shadowRoot!.querySelector<HTMLDivElement>('.export'); 221 exportDiv && 222 (exportDiv.onclick = (): void => { 223 this.exportData(); 224 }); 225 } 226 227 exportData(): void { 228 exportData(this); 229 } 230 231 exportExcelData(): void { 232 let now = Date.now(); 233 ExcelFormater.testExport( 234 [ 235 { 236 columns: this.columns as any[], 237 tables: this.ds, 238 sheetName: `${now}`, 239 }, 240 ], 241 `${now}` 242 ); 243 } 244 245 formatExportData(dataSource: any[]): any[] { 246 return formatExportData(dataSource, this); 247 } 248 249 formatExportCsvData(dataSource: any[]): string { 250 if (dataSource === undefined || dataSource.length === 0) { 251 return ''; 252 } 253 if (this.columns == undefined) { 254 return ''; 255 } 256 let str = ''; 257 str += this.columns!.map((column) => { 258 let dataIndex = column.getAttribute('data-index'); 259 let columnName = column.getAttribute('title'); 260 if (columnName === '') { 261 columnName = dataIndex; 262 } 263 return columnName; 264 }).join(','); 265 str += recursionExportTableData(this.columns, dataSource); 266 return str; 267 } 268 269 injectColumns(): void { 270 this.columns = this.st!.assignedElements(); 271 this.columns.forEach((column) => { 272 if (column.tagName === 'LIT-TABLE-COLUMN') { 273 this.gridTemplateColumns.push(column.getAttribute('width') || '1fr'); 274 } 275 }); 276 } 277 278 setStatus(list: any, status: boolean, depth: number = 0): void { 279 this.tableElement!.scrollTop = 0; 280 // 添加depth参数,让切换图标的代码在递归中只走一遍 281 if (depth === 0) { 282 if (status) { 283 this.shadowRoot!.querySelector<LitIcon>('.top')!.name = 'down'; 284 this.shadowRoot!.querySelector<LitIcon>('.bottom')!.name = 'up'; 285 } else { 286 this.shadowRoot!.querySelector<LitIcon>('.top')!.name = 'up'; 287 this.shadowRoot!.querySelector<LitIcon>('.bottom')!.name = 'down'; 288 } 289 } 290 for (let item of list) { 291 item.status = status; 292 if (item.children !== undefined && item.children.length > 0) { 293 this.setStatus(item.children, status, depth + 1); 294 } 295 } 296 } 297 298 //当 custom element首次被插入文档DOM时,被调用。 299 connectedCallback(): void { 300 this.st = this.shadowRoot?.querySelector('#slot'); 301 this.tableElement = this.shadowRoot?.querySelector('.table'); 302 this.exportProgress = this.shadowRoot?.querySelector('#export_progress_bar'); 303 this.theadElement = this.shadowRoot?.querySelector('.thead'); 304 this.treeElement = this.shadowRoot?.querySelector('.tree'); 305 this.tbodyElement = this.shadowRoot?.querySelector('.body'); 306 this.tableColumns = this.querySelectorAll<LitTableColumn>('lit-table-column'); 307 this.colCount = this.tableColumns!.length; 308 this.dataExportInit(); 309 addCopyEventListener(this); 310 this.st?.addEventListener('slotchange', () => { 311 this.theadElement!.innerHTML = ''; 312 setTimeout(() => { 313 this.columns = this.st!.assignedElements(); 314 let rowElement = document.createElement('div'); 315 rowElement.classList.add('th'); 316 addSelectAllBox(rowElement, this); 317 let area: Array<any> = []; 318 this.gridTemplateColumns = []; 319 this.resolvingArea(this.columns, 0, 0, area, rowElement); 320 area.forEach((rows, j, array) => { 321 for (let i = 0; i < this.colCount; i++) { 322 if (!rows[i]) rows[i] = array[j - 1][i]; 323 } 324 }); 325 if (this.selectable) { 326 let s = area.map((a) => '"_checkbox_ ' + a.map((aa: any) => aa.t).join(' ') + '"').join(' '); 327 rowElement.style.gridTemplateColumns = '60px ' + this.gridTemplateColumns.join(' '); 328 rowElement.style.gridTemplateRows = `repeat(${area.length},1fr)`; 329 rowElement.style.gridTemplateAreas = s; 330 } else { 331 let s = area.map((a) => '"' + a.map((aa: any) => aa.t).join(' ') + '"').join(' '); 332 rowElement.style.gridTemplateColumns = this.gridTemplateColumns.join(' '); 333 rowElement.style.gridTemplateRows = `repeat(${area.length},1fr)`; 334 rowElement.style.gridTemplateAreas = s; 335 } 336 this.theadElement!.innerHTML = ''; 337 this.theadElement!.append(rowElement); 338 this.treeElement!.style.top = this.theadElement?.clientHeight + 'px'; 339 }); 340 }); 341 this.shadowRoot!.addEventListener('load', function (event) {}); 342 this.tableElement!.addEventListener('mouseout', (ev) => this.mouseOut()); 343 } 344 345 resolvingArea(columns: any, x: any, y: any, area: Array<any>, rowElement: HTMLDivElement) { 346 columns.forEach((a: any, i: any) => { 347 if (!area[y]) area[y] = []; 348 let key = a.getAttribute('key') || a.getAttribute('title'); 349 if (a.tagName === 'LIT-TABLE-GROUP') { 350 let len = a.querySelectorAll('lit-table-column').length; 351 let children = [...a.children].filter((a) => a.tagName !== 'TEMPLATE'); 352 if (children.length > 0) { 353 this.resolvingArea(children, x, y + 1, area, rowElement); 354 } 355 for (let j = 0; j < len; j++) { 356 area[y][x] = { x, y, t: key }; 357 x++; 358 } 359 let h = document.createElement('div'); 360 h.classList.add('td'); 361 h.style.justifyContent = a.getAttribute('align'); 362 h.style.borderBottom = '1px solid #f0f0f0'; 363 h.style.gridArea = key; 364 h.innerText = a.title; 365 if (a.hasAttribute('fixed')) { 366 fixed(h, a.getAttribute('fixed'), '#42b983'); 367 } 368 rowElement.append(h); 369 } else if (a.tagName === 'LIT-TABLE-COLUMN') { 370 area[y][x] = { x, y, t: key }; 371 x++; 372 let head = this.resolvingAreaColumn(rowElement, a, i, key); 373 this.gridTemplateColumns.push(a.getAttribute('width') || '1fr'); 374 let labelArr = a.title.split('/'); 375 for (let i = 0; i < labelArr.length; i++) { 376 let titleLabel = document.createElement('label'); 377 titleLabel.style.cursor = 'pointer'; 378 i == 0 ? (titleLabel.textContent = labelArr[i]) : (titleLabel.textContent = '/' + labelArr[i]); 379 head.appendChild(titleLabel); 380 } 381 if (a.hasAttribute('fixed')) { 382 fixed(head, a.getAttribute('fixed'), '#42b983'); 383 } 384 rowElement.append(head); 385 } 386 }); 387 }; 388 389 resolvingAreaColumn(rowElement: HTMLDivElement, column: any, index: number, key: string): HTMLDivElement { 390 let head: any = document.createElement('div'); 391 head.classList.add('td'); 392 if ((this.hasAttribute('tree') && index > 1) || (!this.hasAttribute('tree') && index > 0)) { 393 let resizeDiv: HTMLDivElement = document.createElement('div'); 394 resizeDiv.classList.add('resize'); 395 head.appendChild(resizeDiv); 396 this.resizeEventHandler(rowElement, resizeDiv, index); 397 } 398 this.resolvingAreaColumnRetract(column, head); 399 this.resolvingAreaColumnOrder(column, index, key, head); 400 this.resolvingAreaColumnButton(column, key, head); 401 head.style.justifyContent = column.getAttribute('align'); 402 head.style.gridArea = key; 403 return head; 404 } 405 406 resolvingAreaColumnRetract(column: any, columnHead: HTMLDivElement): void { 407 if (column.hasAttribute('retract')) { 408 let expand = document.createElement('div'); 409 expand.classList.add('expand'); 410 expand.style.display = 'grid'; 411 columnHead.append(expand); 412 let top = document.createElement('lit-icon') as LitIcon; 413 top.classList.add('top'); 414 top.name = 'up'; 415 expand.append(top); 416 let bottom = document.createElement('lit-icon') as LitIcon; 417 bottom.classList.add('bottom'); 418 bottom.name = 'down'; 419 expand.append(bottom); 420 expand.addEventListener('click', (e) => { 421 if (top.name == 'up' && bottom.name == 'down') { 422 top.name = 'down'; 423 bottom.name = 'up'; 424 // 一键展开 425 this.setStatus(this.value, true); 426 this.recycleDs = this.meauseTreeRowElement(this.value, RedrawTreeForm.Expand); 427 } else { 428 top.name = 'up'; 429 bottom.name = 'down'; 430 // 一键收起 431 this.setStatus(this.value, false); 432 this.recycleDs = this.meauseTreeRowElement(this.value, RedrawTreeForm.Retract); 433 } 434 e.stopPropagation(); 435 }); 436 } 437 } 438 439 resolvingAreaColumnButton(column: any, key: string, head: HTMLDivElement) { 440 if (column.hasAttribute('button')) { 441 let buttonIcon = document.createElement('button'); 442 buttonIcon.innerHTML = 'GetBusyTime(ms)'; 443 buttonIcon.classList.add('button-icon'); 444 head.appendChild(buttonIcon); 445 buttonIcon.addEventListener('click', (event) => { 446 this.dispatchEvent( 447 new CustomEvent('button-click', { 448 detail: { 449 key: key, 450 }, 451 composed: true, 452 }) 453 ); 454 event.stopPropagation(); 455 }); 456 } 457 } 458 459 resolvingAreaColumnOrder(column: any, index: number, key: string,columnHead: any): void { 460 if (column.hasAttribute('order')) { 461 (columnHead as any).sortType = 0; 462 columnHead.classList.add('td-order'); 463 columnHead.style.position = 'relative'; 464 let { upSvg, downSvg } = createDownUpSvg(index, columnHead); 465 columnHead.onclick = () => { 466 if (this.isResize || this.resizeColumnIndex !== -1) { 467 return; 468 } 469 this?.shadowRoot?.querySelectorAll('.td-order svg').forEach((it: any) => { 470 it.setAttribute('fill', 'let(--dark-color1,#212121)'); 471 it.sortType = 0; 472 it.style.display = 'none'; 473 }); 474 if (columnHead.sortType == undefined || columnHead.sortType == null) { 475 columnHead.sortType = 0; 476 } else if (columnHead.sortType === 2) { 477 columnHead.sortType = 0; 478 } else { 479 columnHead.sortType += 1; 480 } 481 upSvg.setAttribute('fill', 'let(--dark-color1,#212121)'); 482 downSvg.setAttribute('fill', 'let(--dark-color1,#212121)'); 483 upSvg.style.display = columnHead.sortType === 1 ? 'block' : 'none'; 484 downSvg.style.display = columnHead.sortType === 2 ? 'block' : 'none'; 485 switch (columnHead.sortType) { 486 case 1: 487 this.theadElement!.setAttribute('sort', ''); 488 break; 489 case 2: 490 break; 491 default: 492 this.theadElement!.removeAttribute('sort'); 493 break; 494 } 495 this.dispatchEvent( 496 new CustomEvent('column-click', { 497 detail: { 498 sort: columnHead.sortType, 499 key: key, 500 }, 501 composed: true, 502 }) 503 ); 504 }; 505 } 506 } 507 508 private isResize: boolean = false; 509 private resizeColumnIndex: number = -1; 510 private resizeDownX: number = 0; 511 private columnMinWidth: number = 50; 512 private beforeResizeWidth: number = 0; 513 514 resizeEventHandler(header: HTMLDivElement, element: HTMLDivElement, index: number): void { 515 this.resizeMouseMoveEventHandler(header); 516 header.addEventListener('mouseup', (event) => { 517 if (!this.columnResizeEnable) return; 518 this.isResize = false; 519 this.resizeDownX = 0; 520 header.style.cursor = 'pointer'; 521 setTimeout(() => { 522 this.resizeColumnIndex = -1; 523 }, 100); 524 event.stopPropagation(); 525 event.preventDefault(); 526 }); 527 header.addEventListener('mouseleave', (event) => { 528 if (!this.columnResizeEnable) return; 529 event.stopPropagation(); 530 event.preventDefault(); 531 this.isResize = false; 532 this.resizeDownX = 0; 533 this.resizeColumnIndex = -1; 534 header.style.cursor = 'pointer'; 535 }); 536 element.addEventListener('mousedown', (event) => { 537 if (event.button === 0) { 538 if (!this.columnResizeEnable) return; 539 this.isResize = true; 540 this.resizeColumnIndex = index; 541 this.resizeDownX = event.clientX; 542 let pre = header.childNodes.item(this.resizeColumnIndex - 1) as HTMLDivElement; 543 this.beforeResizeWidth = pre.clientWidth; 544 event.stopPropagation(); 545 } 546 }); 547 element.addEventListener('click', (event) => { 548 event.stopPropagation(); 549 }); 550 } 551 552 resizeMouseMoveEventHandler(header: HTMLDivElement) { 553 header.addEventListener('mousemove', (event) => { 554 if (!this.columnResizeEnable) return; 555 if (this.isResize) { 556 let width = event.clientX - this.resizeDownX; 557 header.style.cursor = 'col-resize'; 558 let preWidth = Math.max(this.beforeResizeWidth + width, this.columnMinWidth); 559 for (let i = 0; i < header.childNodes.length; i++) { 560 let node = header.childNodes.item(i) as HTMLDivElement; 561 this.gridTemplateColumns[i] = `${node.clientWidth}px`; 562 } 563 this.gridTemplateColumns[this.resizeColumnIndex - 1] = `${preWidth}px`; 564 header.style.gridTemplateColumns = this.gridTemplateColumns.join(' '); 565 let preNode = header.childNodes.item(this.resizeColumnIndex - 1) as HTMLDivElement; 566 preNode.style.width = `${preWidth}px`; 567 this.shadowRoot!.querySelectorAll<HTMLDivElement>('.tr').forEach((tr) => { 568 if (this.hasAttribute('tree')) { 569 tr.style.gridTemplateColumns = this.gridTemplateColumns.slice(1).join(' '); 570 } else { 571 tr.style.gridTemplateColumns = this.gridTemplateColumns.join(' '); 572 } 573 }); 574 event.preventDefault(); 575 event.stopPropagation(); 576 } else { 577 header.style.cursor = 'pointer'; 578 } 579 }); 580 } 581 582 adoptedCallback(): void {} 583 584 getCheckRows(): any[] { 585 // @ts-ignore 586 return [...this.shadowRoot!.querySelectorAll('div[class=tr][checked]')] 587 .map((a) => (a as any).data) 588 .map((a) => { 589 delete a['children']; 590 return a; 591 }); 592 } 593 594 deleteRowsCondition(fn: any): void { 595 this.shadowRoot!.querySelectorAll('div[class=tr]').forEach((tr) => { 596 // @ts-ignore 597 if (fn(tr.data)) { 598 tr.remove(); 599 } 600 }); 601 } 602 603 meauseElementHeight(rowData: any): number { 604 return 27; 605 } 606 607 meauseTreeElementHeight(rowData: any, depth: number): number { 608 return 27; 609 } 610 611 getVisibleObjects(list: any[]) { 612 let headHeight = 0; 613 let totalHeight = headHeight; 614 let visibleObjects: TableRowObject[] = []; 615 let itemHandler = (rowData: any, index: number) => { 616 let height = this.meauseElementHeight(rowData); 617 let tableRowObject = new TableRowObject(); 618 tableRowObject.height = height; 619 tableRowObject.top = totalHeight; 620 tableRowObject.data = rowData; 621 tableRowObject.rowIndex = index; 622 if ( 623 Math.max(totalHeight, this.tableElement!.scrollTop + headHeight) <= 624 Math.min(totalHeight + height, this.tableElement!.scrollTop + this.tableElement!.clientHeight + headHeight) 625 ) { 626 let newTableElement = this.addTableElement(tableRowObject, false,false, true, totalHeight); 627 let td = newTableElement?.querySelectorAll('.td'); 628 if (tableRowObject.data.rowName === 'cpu-profiler') { 629 td[0].innerHTML = ''; 630 this.createTextColor(tableRowObject, td[0]); 631 } 632 } 633 totalHeight += height; 634 visibleObjects.push(tableRowObject); 635 }; 636 let realIndex = 0; 637 list.forEach((item, index) => { 638 if (Array.isArray(item)) { 639 item.forEach((rowData, childIndex) => { 640 itemHandler(rowData, realIndex); 641 realIndex++; 642 }); 643 } else { 644 itemHandler(item, index); 645 } 646 }); 647 return { visibleObjects, totalHeight }; 648 } 649 650 meauseAllRowHeight(list: any[]): TableRowObject[] { 651 this.tbodyElement!.innerHTML = ''; 652 this.meauseRowElement = undefined; 653 let head = this.shadowRoot!.querySelector('.th'); 654 this.tbodyElement && (this.tbodyElement.style.width = head?.clientWidth + 'px'); 655 this.currentRecycleList = []; 656 let { visibleObjects, totalHeight } = this.getVisibleObjects(list); 657 this.tbodyElement && (this.tbodyElement.style.height = totalHeight + (this.isScrollXOutSide ? 0 : 0) + 'px'); 658 this.tableElement && 659 (this.tableElement.onscroll = (event) => { 660 let tblScrollTop = this.tableElement!.scrollTop; 661 let skip = 0; 662 for (let i = 0; i < visibleObjects.length; i++) { 663 if ( 664 visibleObjects[i].top <= tblScrollTop && 665 visibleObjects[i].top + visibleObjects[i].height >= tblScrollTop 666 ) { 667 skip = i; 668 break; 669 } 670 } 671 let reduce = this.currentRecycleList.map((item) => item.clientHeight).reduce((a, b) => a + b, 0); 672 if (reduce == 0) { 673 return; 674 } 675 while (reduce <= this.tableElement!.clientHeight) { 676 let newTableElement = this.addTableElement(visibleObjects[skip], false,false, false); 677 reduce += newTableElement.clientHeight; 678 } 679 for (let i = 0; i < this.currentRecycleList.length; i++) { 680 this.freshLineHandler(i, skip, visibleObjects); 681 } 682 }); 683 return visibleObjects; 684 } 685 686 freshLineHandler(index: number, skip: number, visibleObjects: TableRowObject[]){ 687 this.freshCurrentLine(this.currentRecycleList[index], visibleObjects[index + skip]); 688 if (visibleObjects[index + skip]) { 689 if (visibleObjects[index + skip].data.rowName === 'cpu-profiler') { 690 this.createTextColor(visibleObjects[index + skip], this.currentRecycleList[index].childNodes[0]); 691 } 692 } 693 } 694 695 newTableRowObject(item: any, totalHeight: number, depth: number, parentNode?: TableRowObject): TableRowObject { 696 let tableRowObject = new TableRowObject(); 697 tableRowObject.depth = depth; 698 tableRowObject.data = item; 699 tableRowObject.top = totalHeight; 700 tableRowObject.height = this.meauseTreeElementHeight(tableRowObject, depth); 701 if (parentNode) { 702 parentNode!.children.push(tableRowObject); 703 } 704 return tableRowObject; 705 } 706 707 resetAllHeight( 708 list: any[], 709 depth: number, 710 totalHeight: number, 711 visibleObjects: TableRowObject[], 712 parentNode?: TableRowObject, 713 form?: RedrawTreeForm 714 ) { 715 let headHeight = this.theadElement?.clientHeight || 0; 716 list.forEach((item) => { 717 let tableRowObject = this.newTableRowObject(item, totalHeight, depth, parentNode); 718 if (this._mode === TableMode.Expand && form === RedrawTreeForm.Retract && !item.status) { 719 tableRowObject.expanded = false; 720 } else if (this._mode === TableMode.Expand && form === RedrawTreeForm.Default) { 721 tableRowObject.expanded = true; 722 } 723 if ( 724 (this._mode === TableMode.Retract && !item.status) || 725 (this._mode === TableMode.Expand && !item.status && form !== RedrawTreeForm.Expand) 726 ) { 727 tableRowObject.expanded = false; 728 if (item.children != undefined && item.children.length > 0) { 729 this.newTableRowObject(item, totalHeight, depth, tableRowObject); 730 } 731 } 732 if ( 733 Math.max(totalHeight, this.tableElement!.scrollTop) <= 734 Math.min( 735 totalHeight + tableRowObject.height, 736 this.tableElement!.scrollTop + this.tableElement!.clientHeight - headHeight 737 ) 738 ) { 739 this.addTableElement(tableRowObject,true, false, true, totalHeight); 740 } 741 totalHeight += tableRowObject.height; 742 visibleObjects.push(tableRowObject); 743 this.resetAllHeightChildrenHandler(item, depth, totalHeight, visibleObjects, tableRowObject, form); 744 }); 745 } 746 747 resetAllHeightChildrenHandler( 748 item: any, 749 depth: number, 750 totalHeight: number, 751 visibleObjects: TableRowObject[], 752 tableRowObject?: TableRowObject, 753 form?: RedrawTreeForm 754 ) { 755 if (item.hasNext) { 756 // js memory的表格 757 if (item.parents != undefined && item.parents.length > 0 && item.status) { 758 this.resetAllHeight(item.parents, depth + 1, totalHeight, visibleObjects, tableRowObject); 759 } else if (item.children != undefined && item.children.length > 0 && item.status) { 760 this.resetAllHeight(item.children, depth + 1, totalHeight, visibleObjects, tableRowObject); 761 } 762 } else { 763 // 其他数据 764 if ( 765 item.children != undefined && 766 item.children.length > 0 && 767 form === RedrawTreeForm.Expand && 768 this._mode === TableMode.Expand 769 ) { 770 item.status = true; 771 this.resetAllHeight(item.children, depth + 1, totalHeight, visibleObjects, tableRowObject); 772 } else if (item.children != undefined && item.children.length > 0 && item.status) { 773 this.resetAllHeight(item.children, depth + 1, totalHeight, visibleObjects, tableRowObject); 774 } 775 } 776 } 777 778 measureReset(): void { 779 this.meauseRowElement = undefined; 780 this.tbodyElement!.innerHTML = ''; 781 this.treeElement!.innerHTML = ''; 782 this.currentRecycleList = []; 783 this.currentTreeDivList = []; 784 } 785 786 meauseTreeRowElement(list: any[], form?: RedrawTreeForm): TableRowObject[] { 787 this.measureReset(); 788 let visibleObjects: TableRowObject[] = []; 789 let totalHeight = 0; 790 this.resetAllHeight(list,0, totalHeight, visibleObjects); 791 this.tbodyElement && (this.tbodyElement.style.height = totalHeight + 'px'); 792 this.treeElement!.style.height = this.tableElement!.clientHeight - this.theadElement!.clientHeight + 'px'; 793 this.tableElement && 794 (this.tableElement.onscroll = (event) => { 795 let visibleObjects = this.recycleDs.filter((item) => { 796 return !item.rowHidden; 797 }); 798 let top = this.tableElement!.scrollTop; 799 this.treeElement!.style.transform = `translateY(${top}px)`; 800 let skip = 0; 801 for (let index = 0; index < visibleObjects.length; index++) { 802 if (visibleObjects[index].top <= top && visibleObjects[index].top + visibleObjects[index].height >= top) { 803 skip = index; 804 break; 805 } 806 } 807 // 如果滚动高度大于数据全部收起的高度,并且this.currentRecycleList数组长度为0要给this.currentRecycleList赋值,不然tab页没有数据 808 if ( 809 visibleObjects[0] && 810 this.tableElement!.scrollTop >= this.value.length * visibleObjects[0].height && 811 this.currentRecycleList.length === 0 812 ) { 813 this.addTableElement(visibleObjects[skip], true, false, false); 814 } 815 let reduce = this.currentRecycleList.map((item) => item.clientHeight).reduce((a, b) => a + b, 0); 816 if (reduce == 0) { 817 return; 818 } 819 while (reduce <= this.tableElement!.clientHeight) { 820 let newTableElement = this.addTableElement(visibleObjects[skip], true, false, false); 821 reduce += newTableElement.clientHeight; 822 } 823 for (let i = 0; i < this.currentRecycleList.length; i++) { 824 this.freshCurrentLine( 825 this.currentRecycleList[i], 826 visibleObjects[i + skip], 827 this.treeElement?.children[i] as HTMLElement 828 ); 829 } 830 }); 831 return visibleObjects; 832 } 833 834 private addTableElement( 835 rowData: TableRowObject, 836 isTree: boolean, 837 last: boolean, 838 translate: boolean, 839 totalHeight?: number 840 ) { 841 let newTableElement; 842 if (isTree) { 843 newTableElement = this.createNewTreeTableElement(rowData); 844 } else { 845 newTableElement = this.createNewTableElement(rowData); 846 } 847 if (translate) { 848 newTableElement.style.transform = `translateY(${totalHeight}px)`; 849 } 850 this.tbodyElement?.append(newTableElement); 851 if (last) { 852 if (this.hasAttribute('tree')) { 853 if (this.treeElement?.lastChild) { 854 (this.treeElement?.lastChild as HTMLElement).style.height = rowData.height + 'px'; 855 } 856 } 857 } 858 this.currentRecycleList.push(newTableElement); 859 return newTableElement; 860 } 861 862 createNewTreeTableElement(rowData: TableRowObject): any { 863 let rowTreeElement = document.createElement('div'); 864 rowTreeElement.classList.add('tr'); 865 let treeTop = 0; 866 if (this.treeElement!.children?.length > 0) { 867 let transX = Number((this.treeElement?.lastChild as HTMLElement).style.transform.replace(/[^0-9]/gi, '')); 868 treeTop += transX + rowData.height; 869 } 870 this?.columns?.forEach((column: any, index) => { 871 let dataIndex = column.getAttribute('data-index') || '1'; 872 let td: any; 873 if (index === 0) { 874 td = this.firstElementTdHandler(rowTreeElement, dataIndex, rowData, column); 875 } else { 876 td = this.otherElementHandler(dataIndex, rowData, column); 877 rowTreeElement.append(td); 878 } 879 }); 880 let lastChild = this.treeElement?.lastChild as HTMLElement; 881 if (lastChild) { 882 lastChild.style.transform = `translateY(${treeTop}px)`; 883 } 884 (rowTreeElement as any).data = rowData.data; 885 rowTreeElement.style.gridTemplateColumns = this.gridTemplateColumns.slice(1).join(' '); 886 rowTreeElement.style.position = 'absolute'; 887 rowTreeElement.style.top = '0px'; 888 rowTreeElement.style.left = '0px'; 889 rowTreeElement.style.cursor = 'pointer'; 890 this.setHighLight(rowData.data.isSearch, rowTreeElement); 891 this.addRowElementEvent(rowTreeElement, rowData); 892 return rowTreeElement; 893 } 894 895 addRowElementEvent(rowTreeElement: HTMLDivElement, rowData: any): void { 896 rowTreeElement.onmouseenter = () => { 897 if ((rowTreeElement as any).data.isSelected) return; 898 let indexOf = this.currentRecycleList.indexOf(rowTreeElement); 899 this.currentTreeDivList.forEach((row) => { 900 row.classList.remove('mouse-in'); 901 }); 902 if (indexOf >= 0 && indexOf < this.treeElement!.children.length) { 903 this.setMouseIn(true, [this.treeElement?.children[indexOf] as HTMLElement]); 904 } 905 }; 906 rowTreeElement.onmouseleave = () => { 907 if ((rowTreeElement as any).data.isSelected) return; 908 let indexOf = this.currentRecycleList.indexOf(rowTreeElement); 909 if (indexOf >= 0 && indexOf < this.treeElement!.children.length) { 910 this.setMouseIn(false, [this.treeElement?.children[indexOf] as HTMLElement]); 911 } 912 }; 913 rowTreeElement.onmouseup = (e: MouseEvent) => { 914 let indexOf = this.currentRecycleList.indexOf(rowTreeElement); 915 this.dispatchRowClickEvent(rowData, [this.treeElement?.children[indexOf] as HTMLElement, rowTreeElement], e); 916 }; 917 } 918 919 firstElementTdHandler(tr: HTMLDivElement, dataIndex: string, row: any, column: any) { 920 let td: any; 921 let text = formatName(dataIndex, row.data[dataIndex], this); 922 if (column.template) { 923 td = column.template.render(row.data).content.cloneNode(true); 924 td.template = column.template; 925 td.title = row.data[dataIndex]; 926 } else { 927 td = document.createElement('div'); 928 if (row.data.rowName === 'js-memory' || row.data.rowName === 'cpu-profiler') { 929 td.innerHTML = ''; 930 } else { 931 td.innerHTML = text; 932 } 933 td.dataIndex = dataIndex; 934 if (text.indexOf('<') === -1) { 935 td.title = text; 936 } 937 } 938 if (row.data.children && row.data.children.length > 0 && !row.data.hasNext) { 939 let btn = this.createExpandBtn(row); 940 td.insertBefore(btn, td.firstChild); 941 } 942 if (row.data.hasNext) { 943 td.title = row.data.objectName; 944 let btn = this.createBtn(row); 945 td.insertBefore(btn, td.firstChild); 946 } 947 td.style.paddingLeft = row.depth * iconWidth + 'px'; 948 if (!row.data.children || row.data.children.length === 0) { 949 td.style.paddingLeft = iconWidth * row.depth + iconWidth + iconPadding * 2 + 'px'; 950 } 951 this.jsMemoryHandler(row, td); 952 if (row.data.rowName === 'cpu-profiler') { 953 this.createTextColor(row, td); 954 } 955 (td as any).data = row.data; 956 td.classList.add('tree-first-body'); 957 td.style.position = 'absolute'; 958 td.style.top = '0px'; 959 td.style.left = '0px'; 960 this.addFirstElementEvent(td, tr, row); 961 this.setHighLight(row.data.isSearch, td); 962 this.treeElement!.style.width = column.getAttribute('width'); 963 this.treeElement?.append(td); 964 this.currentTreeDivList.push(td); 965 return td; 966 } 967 968 addFirstElementEvent(td: HTMLDivElement, tr: HTMLDivElement, rowData: any): void { 969 td.onmouseenter = () => { 970 let indexOf = this.currentTreeDivList.indexOf(td); 971 this.currentRecycleList.forEach((row) => { 972 row.classList.remove('mouse-in'); 973 }); 974 if (indexOf >= 0 && indexOf < this.currentRecycleList.length && td.innerHTML != '') { 975 this.setMouseIn(true, [tr]); 976 } 977 }; 978 td.onmouseleave = () => { 979 let indexOf = this.currentTreeDivList.indexOf(td); 980 if (indexOf >= 0 && indexOf < this.currentRecycleList.length) { 981 this.setMouseIn(false, [tr]); 982 } 983 }; 984 td.onmouseup = (e: MouseEvent) => { 985 let indexOf = this.currentTreeDivList.indexOf(td); 986 this.dispatchRowClickEvent(rowData, [td, tr], e); 987 }; 988 } 989 990 otherElementHandler(dataIndex: string, rowData: any, column: any) { 991 let tdDiv: any = document.createElement('div'); 992 tdDiv.classList.add('td'); 993 tdDiv.style.overflow = 'hidden'; 994 tdDiv.style.textOverflow = 'ellipsis'; 995 tdDiv.style.whiteSpace = 'nowrap'; 996 let text = formatName(dataIndex, rowData.data[dataIndex], this); 997 if (text.indexOf('<') === -1) { 998 if (dataIndex === 'selfTimeStr' && rowData.data.chartFrameChildren) { 999 tdDiv.title = rowData.data.selfTime + 'ns'; 1000 } else if (dataIndex === 'totalTimeStr' && rowData.data.chartFrameChildren) { 1001 tdDiv.title = rowData.data.totalTime + 'ns'; 1002 } else { 1003 tdDiv.title = text; 1004 } 1005 } 1006 tdDiv.dataIndex = dataIndex; 1007 tdDiv.style.justifyContent = column.getAttribute('align') || 'flex-start'; 1008 if (column.template) { 1009 tdDiv.appendChild(column.template.render(rowData.data).content.cloneNode(true)); 1010 tdDiv.template = column.template; 1011 } else { 1012 tdDiv.innerHTML = text; 1013 } 1014 return tdDiv; 1015 } 1016 1017 createNewTableElement(rowData: any): any { 1018 let newTableElement = document.createElement('div'); 1019 newTableElement.classList.add('tr'); 1020 this?.columns?.forEach((column: any) => { 1021 let dataIndex = column.getAttribute('data-index') || '1'; 1022 let td = this.createColumnTd(dataIndex, column, rowData); 1023 newTableElement.append(td); 1024 }); 1025 newTableElement.onmouseup = (e: MouseEvent) => { 1026 this.dispatchRowClickEvent(rowData, [newTableElement], e); 1027 }; 1028 newTableElement.onmouseenter = () => { 1029 this.dispatchRowHoverEvent(rowData, [newTableElement]); 1030 }; 1031 if (rowData.data.isSelected != undefined) { 1032 this.setSelectedRow(rowData.data.isSelected, [newTableElement]); 1033 } 1034 (newTableElement as any).data = rowData.data; 1035 newTableElement.style.cursor = 'pointer'; 1036 newTableElement.style.gridTemplateColumns = this.gridTemplateColumns.join(' '); 1037 newTableElement.style.position = 'absolute'; 1038 newTableElement.style.top = '0px'; 1039 newTableElement.style.left = '0px'; 1040 if (this.getItemTextColor) { 1041 newTableElement.style.color = this.getItemTextColor(rowData.data); 1042 } 1043 return newTableElement; 1044 } 1045 1046 createColumnTd(dataIndex: string, column: any, rowData: any): any { 1047 let td: any; 1048 td = document.createElement('div'); 1049 td.classList.add('td'); 1050 td.style.overflow = 'hidden'; 1051 td.style.textOverflow = 'ellipsis'; 1052 td.style.whiteSpace = 'nowrap'; 1053 td.dataIndex = dataIndex; 1054 td.style.justifyContent = column.getAttribute('align') || 'flex-start'; 1055 let text = formatName(dataIndex, rowData.data[dataIndex], this); 1056 if (text.indexOf('<') === -1) { 1057 if (dataIndex === 'totalTimeStr' && rowData.data.chartFrameChildren) { 1058 td.title = rowData.data.totalTime + 'ns'; 1059 } else { 1060 td.title = text; 1061 } 1062 } 1063 // 如果表格中有模板的情况,将模板中的数据放进td中,没有模板,直接将文本放进td 1064 // 但是对于Current Selection tab页来说,表格前两列是时间,第三列是input标签,第四列是button标签 1065 // 而第一行的数据只有第四列一个button,和模板中的数据并不一样,所以要特别处理一下 1066 if (column.template) { 1067 if (dataIndex === 'color' && rowData.data.colorEl === undefined) { 1068 td.innerHTML = ''; 1069 td.template = ''; 1070 } else if (dataIndex === 'operate' && rowData.data.operate && rowData.data.operate.innerHTML === 'RemoveAll') { 1071 let removeAll = document.createElement('button'); 1072 removeAll.className = 'removeAll'; 1073 removeAll.innerHTML = 'RemoveAll'; 1074 removeAll.style.background = 'var(--dark-border1,#262f3c)'; 1075 removeAll.style.color = 'white'; 1076 removeAll.style.borderRadius = '10px'; 1077 removeAll.style.fontSize = '10px'; 1078 removeAll.style.height = '18px'; 1079 removeAll.style.lineHeight = '18px'; 1080 removeAll.style.minWidth = '7em'; 1081 removeAll.style.border = 'none'; 1082 removeAll.style.cursor = 'pointer'; 1083 removeAll.style.outline = 'inherit'; 1084 td.appendChild(removeAll); 1085 } else { 1086 td.appendChild(column.template.render(rowData.data).content.cloneNode(true)); 1087 td.template = column.template; 1088 } 1089 } else { 1090 td.innerHTML = text; 1091 } 1092 return td; 1093 } 1094 1095 createBtn(rowData: any): any { 1096 let btn: any = document.createElement('lit-icon'); 1097 btn.classList.add('tree-icon'); 1098 if (rowData.data.expanded) { 1099 btn.name = 'plus-square'; 1100 } else { 1101 btn.name = 'minus-square'; 1102 } 1103 btn.addEventListener('mouseup', (e: MouseEvent) => { 1104 if (e.button === 0) { 1105 rowData.data.status = false; 1106 const resetNodeHidden = (hidden: boolean, rowData: any) => { 1107 if (hidden) { 1108 rowData.children.forEach((child: any) => { 1109 child.rowHidden = false; 1110 }); 1111 } else { 1112 rowData.children.forEach((child: any) => { 1113 child.rowHidden = true; 1114 resetNodeHidden(hidden, child); 1115 }); 1116 } 1117 }; 1118 1119 if (rowData.data.expanded) { 1120 rowData.data.status = true; 1121 this.dispatchRowClickEventIcon(rowData, [btn]); 1122 rowData.data.expanded = false; 1123 resetNodeHidden(true, rowData); 1124 } else { 1125 rowData.data.expanded = true; 1126 rowData.data.status = false; 1127 resetNodeHidden(false, rowData); 1128 } 1129 this.reMeauseHeight(); 1130 } 1131 e.stopPropagation(); 1132 }); 1133 return btn; 1134 } 1135 1136 resetExpandNodeHidden = (hidden: boolean, rowData: any) => { 1137 if (rowData.children.length > 0) { 1138 if (hidden) { 1139 rowData.children.forEach((child: any) => { 1140 child.rowHidden = true; 1141 this.resetExpandNodeHidden(hidden, child); 1142 }); 1143 } else { 1144 rowData.children.forEach((child: any) => { 1145 child.rowHidden = !rowData.expanded; 1146 if (rowData.expanded) { 1147 this.resetExpandNodeHidden(hidden, child); 1148 } 1149 }); 1150 } 1151 } 1152 }; 1153 1154 setChildrenStatus(rowData:any, data: any) { 1155 for (let d of data) { 1156 if (rowData.data === d) { 1157 d.status = false; 1158 } 1159 if (d.children != undefined && d.children.length > 0) { 1160 this.setChildrenStatus(rowData, d.children); 1161 } 1162 } 1163 } 1164 createExpandBtn(rowData: any): any { 1165 let btn: any = document.createElement('lit-icon'); 1166 btn.classList.add('tree-icon'); 1167 // @ts-ignore 1168 if (rowData.expanded) { 1169 btn.name = 'minus-square'; 1170 } else { 1171 btn.name = 'plus-square'; 1172 } 1173 btn.onmouseup = (e: MouseEvent) => { 1174 if (e.button === 0) { 1175 if (rowData.expanded && this._mode === TableMode.Retract) { 1176 rowData.data.status = false; 1177 rowData.expanded = false; 1178 this.resetExpandNodeHidden(true, rowData); 1179 } else if (!rowData.expanded && this._mode === TableMode.Retract) { 1180 rowData.expanded = true; 1181 rowData.data.status = true; 1182 this.recycleDs = this.meauseTreeRowElement(this.value, RedrawTreeForm.Retract); 1183 this.resetExpandNodeHidden(false, rowData); 1184 } 1185 if (this._mode === TableMode.Expand && rowData.expanded) { 1186 // 点击收起的时候将点击的那条数据的status改为false 1187 this.setChildrenStatus(rowData, this.value); 1188 rowData.expanded = false; 1189 this.resetExpandNodeHidden(true, rowData); 1190 } else if (this._mode === TableMode.Expand && !rowData.expanded) { 1191 if (rowData.data.children) { 1192 rowData.data.status = true; 1193 } 1194 this.recycleDs = this.meauseTreeRowElement(this.value, RedrawTreeForm.Default); 1195 rowData.expanded = true; 1196 this.resetExpandNodeHidden(false, rowData); 1197 } 1198 this.reMeauseHeight(); 1199 } 1200 e.stopPropagation(); 1201 }; 1202 return btn; 1203 } 1204 1205 reMeauseHeight(): void { 1206 if (this.currentRecycleList.length === 0) { 1207 return; 1208 } 1209 let totalHeight = 0; 1210 this.recycleDs.forEach((it) => { 1211 if (!it.rowHidden) { 1212 it.top = totalHeight; 1213 totalHeight += it.height; 1214 } 1215 }); 1216 this.tbodyElement && (this.tbodyElement.style.height = totalHeight + (this.isScrollXOutSide ? 0 : 0) + 'px'); 1217 this.treeElement!.style.height = this.tableElement!.clientHeight - this.theadElement!.clientHeight + 'px'; 1218 let visibleObjects = this.recycleDs.filter((item) => { 1219 return !item.rowHidden; 1220 }); 1221 let top = this.tableElement!.scrollTop; 1222 let skip = 0; 1223 for (let i = 0; i < visibleObjects.length; i++) { 1224 if (visibleObjects[i].top <= top && visibleObjects[i].top + visibleObjects[i].height >= top) { 1225 skip = i; 1226 break; 1227 } 1228 } 1229 let reduce = this.currentRecycleList.map((item) => item.clientHeight).reduce((a, b) => a + b, 0); 1230 if (reduce === 0) { 1231 return; 1232 } 1233 while (reduce <= this.tableElement!.clientHeight + 1) { 1234 let isTree = this.hasAttribute('tree'); 1235 let newTableElement = this.addTableElement(visibleObjects[skip], isTree, isTree, false); 1236 reduce += newTableElement.clientHeight; 1237 } 1238 for (let i = 0; i < this.currentRecycleList.length; i++) { 1239 if (this.hasAttribute('tree')) { 1240 this.freshCurrentLine( 1241 this.currentRecycleList[i], 1242 visibleObjects[i + skip], 1243 this.treeElement?.children[i] as HTMLElement 1244 ); 1245 } else { 1246 this.freshLineHandler(i, skip, visibleObjects); 1247 } 1248 } 1249 } 1250 1251 getWheelStatus(element: any): void { 1252 element.addEventListener('wheel', (event: WheelEvent) => { 1253 if (element.scrollWidth !== element.offsetWidth) { 1254 event.preventDefault(); 1255 } 1256 element.scrollLeft += event.deltaY; 1257 }); 1258 } 1259 1260 renderTable(): void { 1261 if (!this.columns) { 1262 return; 1263 } 1264 if (!this.ds) { 1265 return; 1266 } // If no data source is set, it is returned directly 1267 this.normalDs = []; 1268 this.tbodyElement!.innerHTML = ''; // Clear the table contents 1269 this.ds.forEach((rowData: any) => { 1270 let tblRowElement = document.createElement('div'); 1271 tblRowElement.classList.add('tr'); 1272 // @ts-ignore 1273 tblRowElement.data = rowData; 1274 let gridTemplateColumns: Array<any> = []; 1275 // If the table is configured with selectable (select row mode) add a checkbox at the head of the line alone 1276 this.renderTableRowSelect(tblRowElement); 1277 this.tableColumns!.forEach((tblColumn) => { 1278 tblColumn.addEventListener('contextmenu', (e) => { 1279 e.preventDefault(); 1280 }); 1281 let dataIndex = tblColumn.getAttribute('data-index') || '1'; 1282 gridTemplateColumns.push(tblColumn.getAttribute('width') || '1fr'); 1283 this.renderTableRowColumnElement(tblColumn, tblRowElement, dataIndex, rowData); 1284 }); 1285 if (this.selectable) { 1286 // If the table with selection is preceded by a 60px column 1287 tblRowElement.style.gridTemplateColumns = '60px ' + gridTemplateColumns.join(' '); 1288 } else { 1289 tblRowElement.style.gridTemplateColumns = gridTemplateColumns.join(' '); 1290 } 1291 this.renderTableRowElementEvent(tblRowElement, rowData); 1292 this.normalDs.push(tblRowElement); 1293 this.tbodyElement!.append(tblRowElement); 1294 }); 1295 } 1296 1297 renderTableRowSelect(tblRowElement: HTMLDivElement): void { 1298 if (this.selectable) { 1299 let tblBox = document.createElement('div'); 1300 tblBox.style.display = 'flex'; 1301 tblBox.style.justifyContent = 'center'; 1302 tblBox.style.alignItems = 'center'; 1303 tblBox.classList.add('td'); 1304 let checkbox = document.createElement('lit-checkbox'); 1305 checkbox.classList.add('row-checkbox'); 1306 checkbox.onchange = (e: any) => { 1307 // Checkbox checking affects whether the div corresponding to the row has a checked attribute for marking 1308 if (e.detail.checked) { 1309 tblRowElement.setAttribute('checked', ''); 1310 } else { 1311 tblRowElement.removeAttribute('checked'); 1312 } 1313 }; 1314 this.getWheelStatus(tblBox); 1315 tblBox.appendChild(checkbox); 1316 tblRowElement.appendChild(tblBox); 1317 } 1318 } 1319 1320 renderTableRowColumnElement(tblColumn: LitTableColumn, tblRowElement: HTMLDivElement, dataIndex: string, rowData: any): void { 1321 if (tblColumn.template) { 1322 // If you customize the rendering, you get the nodes from the template 1323 // @ts-ignore 1324 let cloneNode = tblColumn.template.render(rowData).content.cloneNode(true); 1325 let tblCustomDiv = document.createElement('div'); 1326 tblCustomDiv.classList.add('td'); 1327 tblCustomDiv.style.wordBreak = 'break-all'; 1328 tblCustomDiv.style.whiteSpace = 'pre-wrap'; 1329 tblCustomDiv.style.justifyContent = tblColumn.getAttribute('align') || ''; 1330 if (tblColumn.hasAttribute('fixed')) { 1331 fixed(tblCustomDiv, tblColumn.getAttribute('fixed') || '', '#ffffff'); 1332 } 1333 this.getWheelStatus(tblCustomDiv); 1334 tblCustomDiv.append(cloneNode); 1335 tblRowElement.append(tblCustomDiv); 1336 } else { 1337 let tblDiv = document.createElement('div'); 1338 tblDiv.classList.add('td'); 1339 tblDiv.style.wordBreak = 'break-all'; 1340 tblDiv.style.whiteSpace = 'pre-wrap'; 1341 tblDiv.title = rowData[dataIndex]; 1342 tblDiv.style.justifyContent = tblColumn.getAttribute('align') || ''; 1343 if (tblColumn.hasAttribute('fixed')) { 1344 fixed(tblDiv, tblColumn.getAttribute('fixed') || '', '#ffffff'); 1345 } 1346 this.getWheelStatus(tblDiv); 1347 tblDiv.innerHTML = formatName(dataIndex, rowData[dataIndex], this); 1348 tblRowElement.append(tblDiv); 1349 } 1350 } 1351 1352 renderTableRowElementEvent(tblRowElement: HTMLDivElement, rowData: any): void { 1353 tblRowElement.onmouseup = (e: MouseEvent) => { 1354 this.dispatchEvent( 1355 new CustomEvent('row-click', { 1356 detail: { 1357 rowData, 1358 data: rowData, 1359 callBack: (isSelected: boolean) => { 1360 //是否爲单选 1361 if (isSelected) { 1362 this.clearAllSelection(rowData); 1363 } 1364 this.setSelectedRow(rowData.isSelected, [tblRowElement]); 1365 }, 1366 }, 1367 composed: true, 1368 }) 1369 ); 1370 }; 1371 } 1372 1373 freshCurrentLine(element: HTMLElement, rowObject: TableRowObject, firstElement?: HTMLElement): void { 1374 if (!rowObject) { 1375 if (firstElement) { 1376 firstElement.style.display = 'none'; 1377 } 1378 element.style.display = 'none'; 1379 return; 1380 } 1381 let childIndex = -1; 1382 this.setHighLight(rowObject.data.isSearch, element); 1383 element.childNodes.forEach((child) => { 1384 if (child.nodeType != 1) return; 1385 childIndex++; 1386 let idx = firstElement !== undefined ? childIndex + 1 : childIndex; 1387 this.freshLineFirstElementHandler(firstElement, rowObject, childIndex); 1388 if (idx < this.columns!.length) { 1389 let dataIndex = this.columns![idx].getAttribute('data-index') || '1'; 1390 let text = formatName(dataIndex, rowObject.data[dataIndex], this); 1391 if ((this.columns![idx] as any).template) { 1392 (child as HTMLElement).innerHTML = ''; 1393 (child as HTMLElement).appendChild( 1394 (this.columns![idx] as any).template.render(rowObject.data).content.cloneNode(true) 1395 ); 1396 (child as HTMLElement).title = text; 1397 } else { 1398 (child as HTMLElement).innerHTML = text; 1399 if (dataIndex === 'selfTimeStr' && rowObject.data.chartFrameChildren) { 1400 (child as HTMLElement).title = rowObject.data.selfTime + 'ns'; 1401 } else if (dataIndex === 'totalTimeStr' && rowObject.data.chartFrameChildren) { 1402 (child as HTMLElement).title = rowObject.data.totalTime + 'ns'; 1403 } else if (dataIndex === 'timeStr' && rowObject.data instanceof JsCpuProfilerStatisticsStruct) { 1404 (child as HTMLElement).title = rowObject.data.time + 'ns'; 1405 } else { 1406 (child as HTMLElement).title = text; 1407 } 1408 } 1409 } 1410 }); 1411 this.freshLineStyleAndEvents(element, rowObject, firstElement); 1412 } 1413 1414 freshLineStyleAndEvents(element: HTMLElement, rowObject: TableRowObject, firstElement?: HTMLElement): void { 1415 if (element.style.display === 'none') { 1416 element.style.display = 'grid'; 1417 } 1418 element.style.transform = `translateY(${rowObject.top}px)`; 1419 if (firstElement && firstElement.style.display === 'none') { 1420 firstElement.style.display = 'flex'; 1421 } 1422 element.onmouseup = (e: MouseEvent) => { 1423 if (firstElement !== undefined) { 1424 this.dispatchRowClickEvent(rowObject, [firstElement, element], e); 1425 } else { 1426 this.dispatchRowClickEvent(rowObject, [element], e); 1427 } 1428 }; 1429 element.onmouseenter = () => { 1430 this.dispatchRowHoverEvent(rowObject, [element]); 1431 }; 1432 (element as any).data = rowObject.data; 1433 if (rowObject.data.isSelected !== undefined) { 1434 this.setSelectedRow(rowObject.data.isSelected, [element]); 1435 } else { 1436 this.setSelectedRow(false, [element]); 1437 } 1438 if (rowObject.data.isHover !== undefined) { 1439 this.setMouseIn(rowObject.data.isHover, [element]); 1440 } else { 1441 this.setMouseIn(false, [element]); 1442 } 1443 if (this.getItemTextColor) { 1444 element.style.color = this.getItemTextColor((element as any).data); 1445 } 1446 } 1447 1448 freshLineFirstElementHandler(firstElement: any, rowObject: TableRowObject, childIndex: number): void { 1449 if (firstElement !== undefined && childIndex === 0) { 1450 this.setHighLight(rowObject.data.isSearch, firstElement); 1451 (firstElement as any).data = rowObject.data; 1452 if ((this.columns![0] as any).template) { 1453 firstElement.innerHTML = (this.columns![0] as any).template 1454 .render(rowObject.data) 1455 .content.cloneNode(true).innerHTML; 1456 } else { 1457 let dataIndex = this.columns![0].getAttribute('data-index') || '1'; 1458 let text = formatName(dataIndex, rowObject.data[dataIndex], this); 1459 if (rowObject.data.rowName === 'js-memory' || rowObject.data.rowName === 'cpu-profiler') { 1460 firstElement.innerHTML = ''; 1461 } else { 1462 firstElement.innerHTML = text; 1463 } 1464 firstElement.title = text; 1465 } 1466 if (rowObject.children && rowObject.children.length > 0 && !rowObject.data.hasNext) { 1467 let btn = this.createExpandBtn(rowObject); 1468 firstElement.insertBefore(btn, firstElement.firstChild); 1469 } 1470 firstElement.style.paddingLeft = iconWidth * rowObject.depth + 'px'; 1471 if (!rowObject.children || rowObject.children.length === 0) { 1472 firstElement.style.paddingLeft = iconWidth * rowObject.depth + iconWidth + iconPadding * 2 + 'px'; 1473 } 1474 if (rowObject.data.hasNext) { 1475 let btn = this.createBtn(rowObject); 1476 firstElement.title = rowObject.data.objectName; 1477 firstElement.insertBefore(btn, firstElement.firstChild); 1478 firstElement.style.paddingLeft = iconWidth * rowObject.depth + 'px'; 1479 } 1480 this.jsMemoryHandler(rowObject, firstElement); 1481 if (rowObject.data.rowName === 'cpu-profiler') { 1482 this.createTextColor(rowObject, firstElement); 1483 } 1484 firstElement.onmouseup = (e: MouseEvent) => { 1485 this.dispatchRowClickEvent(rowObject, [firstElement, element], e); 1486 }; 1487 firstElement.style.transform = `translateY(${rowObject.top - this.tableElement!.scrollTop}px)`; 1488 if (rowObject.data.isSelected !== undefined) { 1489 this.setSelectedRow(rowObject.data.isSelected, [firstElement]); 1490 } else { 1491 this.setSelectedRow(false, [firstElement]); 1492 } 1493 } 1494 } 1495 1496 setSelectedRow(isSelected: boolean, rows: any[]): void { 1497 if (isSelected) { 1498 rows.forEach((row) => { 1499 if (row.classList.contains('mouse-in')) { 1500 row.classList.remove('mouse-in'); 1501 } 1502 row.classList.add('mouse-select'); 1503 }); 1504 } else { 1505 rows.forEach((row) => { 1506 row.classList.remove('mouse-select'); 1507 }); 1508 } 1509 } 1510 1511 setMouseIn(isMouseIn: boolean, rows: any[]): void { 1512 if (isMouseIn) { 1513 rows.forEach((row) => { 1514 row.classList.add('mouse-in'); 1515 }); 1516 } else { 1517 rows.forEach((row) => { 1518 row.classList.remove('mouse-in'); 1519 }); 1520 } 1521 } 1522 1523 scrollToData(data: any): void { 1524 if (this.isRecycleList) { 1525 if (this.recycleDs.length > 0) { 1526 let filter = this.recycleDs.filter((item) => { 1527 return item.data === data; 1528 }); 1529 if (filter.length > 0) { 1530 this.tableElement!.scrollTop = filter[0].top; 1531 } 1532 this.setCurrentSelection(data); 1533 } 1534 } else { 1535 if (this.normalDs.length > 0) { 1536 let filter = this.normalDs.filter((item) => { 1537 return item.data === data; 1538 }); 1539 if (filter.length > 0) { 1540 this.tableElement!.scrollTop = filter[0].top; 1541 } 1542 } 1543 } 1544 } 1545 1546 expandList(datasource: any[]): void { 1547 let filter = this.recycleDs.filter((item) => { 1548 return datasource.indexOf(item.data) != -1; 1549 }); 1550 if (filter.length > 0) { 1551 filter.forEach((item) => { 1552 item.expanded = true; 1553 item.rowHidden = false; 1554 }); 1555 } 1556 this.reMeauseHeight(); 1557 } 1558 1559 clearAllSelection(rowObjectData: any): void { 1560 if (this.isRecycleList) { 1561 this.recycleDs.forEach((item) => { 1562 if (item.data != rowObjectData && item.data.isSelected) { 1563 item.data.isSelected = false; 1564 } 1565 }); 1566 this.setSelectedRow(false, this.currentTreeDivList); 1567 this.setSelectedRow(false, this.currentRecycleList); 1568 } else { 1569 this.dataSource.forEach((item) => { 1570 if (item != rowObjectData && item.isSelected) { 1571 item.isSelected = false; 1572 } 1573 }); 1574 this.setSelectedRow(false, this.normalDs); 1575 } 1576 } 1577 1578 clearAllHover(rowObjectData: any): void { 1579 if (this.isRecycleList) { 1580 this.recycleDs.forEach((item) => { 1581 if (item.data != rowObjectData && item.data.isHover) { 1582 item.data.isHover = false; 1583 } 1584 }); 1585 this.setMouseIn(false, this.currentTreeDivList); 1586 this.setMouseIn(false, this.currentRecycleList); 1587 } else { 1588 this.dataSource.forEach((item) => { 1589 if (item != rowObjectData && item.isHover) { 1590 item.isHover = false; 1591 } 1592 }); 1593 this.setMouseIn(false, this.normalDs); 1594 } 1595 } 1596 1597 mouseOut(): void { 1598 if (this.isRecycleList) { 1599 this.recycleDs.forEach((item) => (item.data.isHover = false)); 1600 this.setMouseIn(false, this.currentTreeDivList); 1601 this.setMouseIn(false, this.currentRecycleList); 1602 } else { 1603 this.dataSource.forEach((item) => (item.isHover = false)); 1604 this.setMouseIn(false, this.normalDs); 1605 } 1606 this.dispatchEvent( 1607 new CustomEvent('row-hover', { 1608 detail: { 1609 data: undefined, 1610 }, 1611 composed: true, 1612 }) 1613 ); 1614 } 1615 1616 setCurrentSelection(selectionData: any): void { 1617 if (this.isRecycleList) { 1618 if (selectionData.isSelected !== undefined) { 1619 this.currentTreeDivList.forEach((itemEl) => { 1620 if ((itemEl as any).data === selectionData) { 1621 this.setSelectedRow(selectionData.isSelected, [itemEl]); 1622 } 1623 }); 1624 this.currentRecycleList.forEach((recycleItem) => { 1625 if ((recycleItem as any).data === selectionData) { 1626 this.setSelectedRow(selectionData.isSelected, [recycleItem]); 1627 } 1628 }); 1629 } 1630 } else { 1631 if (selectionData.isSelected !== undefined) { 1632 this.normalDs.forEach((item) => { 1633 if ((item as any).data === selectionData) { 1634 this.setSelectedRow(selectionData.isSelected, [item]); 1635 } 1636 }); 1637 } 1638 } 1639 } 1640 1641 setCurrentHover(data: any): void { 1642 if (this.isRecycleList) { 1643 this.setMouseIn(false, this.currentTreeDivList); 1644 this.setMouseIn(false, this.currentRecycleList); 1645 if (data.isHover !== undefined) { 1646 this.currentTreeDivList.forEach((hoverItem) => { 1647 if ((hoverItem as any).data === data) { 1648 this.setMouseIn(data.isHover, [hoverItem]); 1649 } 1650 }); 1651 this.currentRecycleList.forEach((hoverItem) => { 1652 if ((hoverItem as any).data === data) { 1653 this.setMouseIn(data.isHover, [hoverItem]); 1654 } 1655 }); 1656 } 1657 } else { 1658 this.setMouseIn(false, this.normalDs); 1659 if (data.isHover !== undefined) { 1660 this.normalDs.forEach((item): void => { 1661 if ((item as any).data === data) { 1662 this.setMouseIn(data.isHover, [item]); 1663 } 1664 }); 1665 } 1666 } 1667 } 1668 1669 dispatchRowClickEventIcon(rowData: any, elements: any[]): void { 1670 this.dispatchEvent( 1671 new CustomEvent('icon-click', { 1672 detail: { 1673 ...rowData.data, 1674 data: rowData.data, 1675 callBack: (isSelected: boolean): void => { 1676 //是否爲单选 1677 if (isSelected) { 1678 this.clearAllSelection(rowData.data); 1679 } 1680 this.setSelectedRow(rowData.data.isSelected, elements); 1681 }, 1682 }, 1683 composed: true, 1684 }) 1685 ); 1686 } 1687 1688 dispatchRowClickEvent(rowObject: any, elements: any[], event: MouseEvent): void { 1689 this.dispatchEvent( 1690 new CustomEvent('row-click', { 1691 detail: { 1692 button: event.button, 1693 ...rowObject.data, 1694 data: rowObject.data, 1695 callBack: (isSelected: boolean): void => { 1696 //是否爲单选 1697 if (isSelected) { 1698 this.clearAllSelection(rowObject.data); 1699 } 1700 this.setSelectedRow(rowObject.data.isSelected, elements); 1701 }, 1702 }, 1703 composed: true, 1704 }) 1705 ); 1706 } 1707 1708 dispatchRowHoverEvent(rowObject: any, elements: any[]): void { 1709 this.dispatchEvent( 1710 new CustomEvent('row-hover', { 1711 detail: { 1712 data: rowObject.data, 1713 callBack: (): void => { 1714 this.clearAllHover(rowObject.data); 1715 this.setMouseIn(rowObject.data.isHover, elements); 1716 }, 1717 }, 1718 composed: true, 1719 }) 1720 ); 1721 } 1722 1723 setHighLight(isSearch: boolean, element: any): void { 1724 if (isSearch) { 1725 element.setAttribute('high-light', ''); 1726 } else { 1727 element.removeAttribute('high-light'); 1728 } 1729 } 1730 1731 createTextColor(rowData: any, divElement: any): void { 1732 let nodeText = document.createElement('text'); 1733 nodeText.classList.add('functionName'); 1734 nodeText.textContent = rowData.data.name; 1735 divElement.append(nodeText); 1736 if (rowData.data.scriptName !== 'unknown') { 1737 let scriptText = document.createElement('text'); 1738 scriptText.classList.add('scriptName'); 1739 scriptText.textContent = rowData.data.scriptName; 1740 divElement.append(scriptText); 1741 scriptText.style.color = '#a1a1a1'; 1742 } 1743 divElement.title = rowData.data.symbolName; 1744 } 1745 1746 jsMemoryHandler(rowData: any, td: any) { 1747 if (rowData.data.rowName === 'js-memory') { 1748 let nodeText = document.createElement('text'); 1749 nodeText.classList.add('nodeName'); 1750 nodeText.textContent = rowData.data.nodeName; 1751 td.append(nodeText); 1752 let countText = document.createElement('text'); 1753 countText.classList.add('countName'); 1754 countText.textContent = rowData.data.count; 1755 td.append(countText); 1756 let nodeIdText = document.createElement('text'); 1757 nodeIdText.classList.add('nodeIdText'); 1758 nodeIdText.textContent = rowData.data.nodeId; 1759 td.append(nodeIdText); 1760 if (rowData.data.edgeName != '') { 1761 let edgeNameText = document.createElement('text'); 1762 edgeNameText.classList.add('edgeNameText'); 1763 edgeNameText.textContent = rowData.data.edgeName; 1764 td.insertBefore(edgeNameText, nodeText); 1765 let span = document.createElement('span'); 1766 span.classList.add('span'); 1767 if (rowData.data.type === ConstructorType.RetainersType) { 1768 span.textContent = '\xa0' + 'in' + '\xa0'; 1769 nodeIdText.textContent = ` @${rowData.data.id}`; 1770 } else { 1771 span.textContent = '\xa0' + '::' + '\xa0'; 1772 } 1773 edgeNameText.append(span); 1774 } 1775 if ( 1776 (rowData.data.nodeType === NodeType.STRING || 1777 rowData.data.nodeType === NodeType.CONCATENATED_STRING || 1778 rowData.data.nodeType === NodeType.SLICED_STRING) && 1779 rowData.data.type !== ConstructorType.ClassType 1780 ) { 1781 nodeText.style.color = '#d53d3d'; 1782 nodeText.textContent = '"' + rowData.data.nodeName + '"'; 1783 } 1784 td.title = rowData.data.objectName; 1785 } 1786 } 1787} 1788 1789// 表格默认是展开还是收起的 1790export enum TableMode { 1791 Expand, // 默认展开 1792 Retract, // 默认收起 1793} 1794 1795// 重绘的表格是要全部展开,全部收起,还是一层一层手动打开 1796export enum RedrawTreeForm { 1797 Expand, // 一键展开 1798 Retract, // 一键收起 1799 Default, //点击加号,逐层展开 1800} 1801