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 { LitMainMenuGroup } from '../../base-ui/menu/LitMainMenuGroup'; 18import { LitMainMenu, MenuGroup, MenuItem } from '../../base-ui/menu/LitMainMenu'; 19import { LitMainMenuItem } from '../../base-ui/menu/LitMainMenuItem'; 20import { SpStatisticsHttpUtil } from '../../statistics/util/SpStatisticsHttpUtil'; 21import { EventDefinition, eventDefinitions } from '../enums/helpDocEnums'; 22 23@element('sp-help') 24export class SpHelp extends BaseElement { 25 private appContent: HTMLElement | undefined | null; 26 private helpFile: HTMLElement | undefined | null; 27 private navbarContainer: HTMLElement | undefined | null; 28 private backToTop: HTMLElement | undefined | null; 29 30 get dark(): boolean { 31 return this.hasAttribute('dark'); 32 } 33 34 set dark(dark: boolean) { 35 if (dark) { 36 this.setAttribute('dark', `${dark}`); 37 } else { 38 this.removeAttribute('dark'); 39 } 40 this.helpFile!.innerHTML = 41 '<object type="text/html" data=' + 42 `/application/doc/quickstart_device_record.html?${dark} width="100%" height="100%"></object>`; 43 this.navbarInit('quickstart_device_record'); 44 } 45 46 initElements(): void { 47 let parentElement = this.parentNode as HTMLElement; 48 parentElement.style.overflow = 'hidden'; 49 this.appContent = this.shadowRoot?.querySelector('#app-content') as HTMLElement; 50 this.helpFile = this.shadowRoot?.querySelector('#help-file') as HTMLElement; 51 this.navbarContainer = this.shadowRoot?.querySelector('#navbar-container') as HTMLElement; 52 this.backToTop = this.shadowRoot?.querySelector('.back') as HTMLElement; 53 let mainMenu = this.shadowRoot?.querySelector('#main-menu') as LitMainMenu; 54 let header = mainMenu.shadowRoot?.querySelector('.header') as HTMLDivElement; 55 let color = mainMenu.shadowRoot?.querySelector('.customColor') as HTMLDivElement; 56 let version = mainMenu.shadowRoot?.querySelector('.version') as HTMLDivElement; 57 color.style.display = 'none'; 58 header.style.display = 'none'; 59 version.style.display = 'none'; 60 this.setupMainMenu(mainMenu, this); 61 mainMenu.style.width = '330px'; 62 let body = mainMenu.shadowRoot?.querySelector('.menu-body') as HTMLDivElement; 63 let groups = body.querySelectorAll<LitMainMenuGroup>('lit-main-menu-group'); 64 groups.forEach((value) => { 65 let items = value.querySelectorAll<LitMainMenuItem>('lit-main-menu-item'); 66 items.forEach((item) => { 67 item.style.width = '330px'; 68 }); 69 if (value.title === 'TraceStreamer') { 70 let items = value.querySelectorAll<LitMainMenuItem>('lit-main-menu-item'); 71 items.forEach((i) => { 72 if (i.title !== 'TraceStreamer数据库说明') { 73 i.style.display = 'none'; 74 } 75 }); 76 } 77 if (value.title === 'SmartPerf') { 78 value.style.display = 'none'; 79 } 80 }); 81 let urlParams = new URL(window.location.href).searchParams; 82 if (urlParams && urlParams.get('action') && urlParams.get('action')!.length > 4) { 83 this.itemHelpClick(urlParams, this); 84 } 85 } 86 87 private itemHelpClick(urlParams: URLSearchParams, that: this): void { 88 if (urlParams.get('action')!.length > 4) { 89 let helpDocIndex = urlParams.get('action')!.substring(5); 90 let helpDocDetail = this.getEventDefinitionByIndex(Number(helpDocIndex)); 91 that.helpFile!.innerHTML = `<object type="text/html" data='/application/doc/${helpDocDetail!.name}.html?${that.dark 92 }' width="100%" height="100%"></object>`; 93 94 this.navbarInit(helpDocDetail!.name); 95 } 96 } 97 98 private getEventDefinitionByIndex(index: number): EventDefinition | null { 99 for (let key in eventDefinitions) { 100 if (eventDefinitions[key].index === index) { 101 return eventDefinitions[key]; 102 } 103 } 104 return null; 105 } 106 107 private setupMainMenu(mainMenu: LitMainMenu, that: this): void { 108 mainMenu.menus = [ 109 { 110 collapsed: false, 111 title: 'QuickStart', 112 second: false, 113 icon: 'caret-down', 114 describe: '', 115 children: [ 116 this.setupCaptureAndImportMenu(that), 117 this.setupMemoryMenu(that), 118 this.setupNativeMenu(that), 119 this.setupTsMenu(that), 120 this.setupAnalysisTemplateMenu(that), 121 this.setupFileMenu(that), 122 this.setupOtherMenu(that), 123 ], 124 }, 125 { 126 collapsed: false, 127 title: 'TraceStreamer', 128 second: false, 129 icon: 'caret-down', 130 describe: '', 131 children: [ 132 this.setupDatabaseMenu(that), 133 this.setupCompileMenu(that), 134 this.setupAnalysisMenu(that), 135 this.setupEventListMenu(that), 136 this.setupToolDescriptionMenu(that), 137 this.setupBinderMenu(that), 138 this.setupWakeUpMenu(that), 139 ], 140 }, 141 { 142 collapsed: false, 143 title: 'SmartPerf', 144 second: false, 145 icon: 'caret-down', 146 describe: '', 147 children: [this.setupSmartPerfMenu(that)], 148 }, 149 ]; 150 } 151 152 private setupCaptureAndImportMenu(that: this): MenuGroup { 153 return { 154 collapsed: false, 155 title: '抓取和导入', 156 describe: '', 157 second: true, 158 icon: 'caret-down', 159 children: [ 160 { 161 title: '设备端抓取trace说明', 162 icon: '', 163 clickHandler: function (item: MenuItem): void { 164 that.handleMemoryMenuItemClick(that, 'record', 'quickstart_device_record', '1'); 165 }, 166 }, 167 { 168 title: 'web端抓取trace说明', 169 icon: '', 170 clickHandler: function (item: MenuItem): void { 171 that.handleMemoryMenuItemClick(that, 'online_record', 'quickstart_web_record', '2'); 172 }, 173 }, 174 { 175 title: 'web端加载trace说明', 176 icon: '', 177 clickHandler: function (item: MenuItem): void { 178 that.handleMemoryMenuItemClick(that, 'load', 'quickstart_systemtrace', '3'); 179 }, 180 }, 181 ], 182 }; 183 } 184 185 private setupOtherMenu(that: this): MenuGroup { 186 return { 187 collapsed: false, 188 title: '其他', 189 describe: '', 190 icon: 'caret-down', 191 second: true, 192 children: this.setupOtherMenuItems(that), 193 }; 194 } 195 private setupOtherMenuItems(that: this): MenuItem[] { 196 return [ 197 this.createSubMenuItem('Sql分析和Metrics说明', 'sql', 'quickstart_sql_metrics', that, '17'), 198 this.createSubMenuItem('HiSystemEvent抓取和展示说明', 'hisys', 'quickstart_hisystemevent', that, '18'), 199 this.createSubMenuItem('sdk抓取和展示说明', 'sdk_record', 'quickstart_sdk', that, '19'), 200 this.createSubMenuItem('调用栈可视化和不同库函数调用占比说明', 'import_so', 'quickstart_Import_so', that, '20'), 201 this.createSubMenuItem('Hilog抓取和展示说明', 'hilog', 'quickstart_hilog', that, '21'), 202 this.createSubMenuItem('Ability Monitor抓取和展示说明', 'ability', 'quickstart_ability_monitor', that, '22'), 203 this.createSubMenuItem('Trace解析能力增强', 'trace_parsing', 'quickstart_parsing_ability', that, '23'), 204 this.createSubMenuItem('应用操作技巧', 'operation_skills', 'quickstart_Application_operation_skills', that, '24'), 205 this.createSubMenuItem('快捷键说明', 'keywords_shortcuts', 'quickstart_keywords_shortcuts', that, '25'), 206 ]; 207 } 208 209 private createSubMenuItem(title: string, event: string, docName: string, that: this, index: string): MenuItem { 210 return { 211 title: title, 212 icon: '', 213 clickHandler: (item: MenuItem): void => { 214 that.handleMemoryMenuItemClick(that, event, docName, index); 215 }, 216 }; 217 } 218 219 private setupMemoryMenu(that: this): MenuGroup { 220 return { 221 collapsed: false, 222 title: '内存', 223 describe: '', 224 icon: 'caret-down', 225 second: true, 226 children: [ 227 { 228 title: 'Js Memory抓取和展示说明', 229 icon: '', 230 clickHandler: function (item: MenuItem): void { 231 that.handleMemoryMenuItemClick(that, 'js_memory', 'quickstart_Js_memory', '4'); 232 }, 233 }, 234 { 235 title: 'Native Memory抓取和展示说明', 236 icon: '', 237 clickHandler: function (item: MenuItem): void { 238 that.handleMemoryMenuItemClick(that, 'native', 'quickstart_native_memory', '5'); 239 }, 240 }, 241 { 242 title: '页内存抓取和展示说明', 243 icon: '', 244 clickHandler: function (item: MenuItem): void { 245 that.handleMemoryMenuItemClick(that, 'virtual_memory', 'quickstart_page_fault', '6'); 246 }, 247 }, 248 { 249 title: '系统内存抓取和展示说明', 250 icon: '', 251 clickHandler: function (item: MenuItem): void { 252 that.handleMemoryMenuItemClick(that, 'memory_template', 'quickstart_memory_template', '7'); 253 }, 254 }, 255 ], 256 }; 257 } 258 259 private handleMemoryMenuItemClick(that: this, event: string, docName: string, index?: string): void { 260 SpStatisticsHttpUtil.addOrdinaryVisitAction({ 261 event: event, 262 action: 'help_doc', 263 }); 264 that.helpFile!.innerHTML = `<object type="text/html" data='/application/doc/${docName}.html?${that.dark}' width="100%" height="100%"></object>`; 265 this.navbarInit(docName); 266 this.changeItemURL(index!); 267 } 268 269 private navbarInit(docName: string): void { 270 fetch(`/application/doc/${docName}.html`) 271 .then(response => response.text()) 272 .then(htmlString => { 273 const parser = new DOMParser(); 274 const doc = parser.parseFromString(htmlString, 'text/html'); 275 276 const hTags = Array.from(doc.body.querySelectorAll('h1, h2, h3, h4, h5, h6')).map((header) => ({ 277 id: header.id, 278 text: header.textContent!.trim() 279 })); 280 this.navbarContainer!.innerHTML = `<ul id="nav-links">${hTags.map(hTag => { 281 let backData: string = ''; 282 if (hTag.id) { 283 backData = `<li class="tooltip"><a id="${hTag.id}" data-full-text="${hTag.text}">${hTag.text}</a><span class="tooltiptext" id="tooltip-${hTag.id}">${hTag.text}</span> 284 </li>`; 285 } 286 return backData; 287 }).join('') 288 }</ul>`; 289 290 let navLinks = this.navbarContainer!.querySelectorAll('#nav-links a'); 291 navLinks.forEach((navLink) => { 292 navLink.addEventListener('click', (e) => { 293 let lis = this.navbarContainer!.querySelectorAll('#nav-links li'); 294 lis.forEach(li => li.classList.remove('active')); 295 navLink.closest('li')!.classList.add('active'); 296 let targetId = navLink.id; 297 e.preventDefault(); 298 this.helpFile!.innerHTML = `<object type="text/html" data='/application/doc/${docName}.html?dark=${this.dark}&targetId=${targetId}' width="100%" height="100%"></object>`; 299 }); 300 }); 301 302 this.backToTop!.querySelector('#back-to-top')!.addEventListener('click', (e) => { 303 e.preventDefault(); 304 navLinks.forEach((navLink) => { 305 navLink.closest('li')?.classList.remove('active'); 306 }); 307 this.helpFile!.innerHTML = `<object type="text/html" data='/application/doc/${docName}.html?dark=${this.dark}' width="100%" height="100%"></object>`; 308 }); 309 310 }) 311 .catch(error => { 312 console.error('Error fetching and modifying HTML:', error); 313 }); 314 } 315 316 private changeItemURL(index: string): void { 317 let url = new URL(window.location.href); 318 let actionParam = url.searchParams.get('action'); 319 let newActionValue = `help_${index}`; 320 if (actionParam) { 321 url.searchParams.set('action', newActionValue); 322 let newURL = url.href; 323 history.pushState({}, '', newURL); 324 } else { 325 history.pushState({}, '', window.location.origin + window.location.pathname); 326 } 327 } 328 329 private setupNativeMenu(that: this): MenuGroup { 330 return { 331 collapsed: false, 332 title: 'Native栈', 333 describe: '', 334 second: true, 335 icon: 'caret-down', 336 children: [ 337 { 338 title: 'HiPerf的抓取和展示说明', 339 icon: '', 340 clickHandler: function (item: MenuItem): void { 341 that.handleMemoryMenuItemClick(that, 'perf', 'quickstart_hiperf', '8'); 342 }, 343 }, 344 ], 345 }; 346 } 347 348 private setupTsMenu(that: this): MenuGroup { 349 return { 350 collapsed: false, 351 title: 'TS栈', 352 describe: '', 353 second: true, 354 icon: 'caret-down', 355 children: [ 356 { 357 title: 'Cpuprofiler抓取和展示说明', 358 icon: '', 359 clickHandler: function (item: MenuItem): void { 360 that.handleMemoryMenuItemClick(that, 'arkts', 'quickstart_arkts', '9'); 361 }, 362 }, 363 ], 364 }; 365 } 366 367 private setupAnalysisTemplateMenu(that: this): MenuGroup { 368 return { 369 collapsed: false, 370 title: '分析模板', 371 describe: '', 372 second: true, 373 icon: 'caret-down', 374 children: [ 375 { 376 title: 'Frame timeline抓取和展示说明', 377 icon: '', 378 clickHandler: function (item: MenuItem): void { 379 that.handleMemoryMenuItemClick(that, 'frame_record', 'quickstart_Frametimeline', '10'); 380 }, 381 }, 382 { 383 title: 'Animation的抓取和展示说明', 384 icon: '', 385 clickHandler: function (item: MenuItem): void { 386 that.handleMemoryMenuItemClick(that, 'animation', 'quickstart_animation', '11'); 387 }, 388 }, 389 { 390 title: 'TaskPool抓取和展示说明', 391 icon: '', 392 clickHandler: function (item: MenuItem): void { 393 that.handleMemoryMenuItemClick(that, 'taskpool', 'quickstart_taskpool', '12'); 394 }, 395 }, 396 { 397 title: 'App startup的抓取和展示说明', 398 icon: '', 399 clickHandler: function (item: MenuItem): void { 400 that.handleMemoryMenuItemClick(that, 'app_startup', 'quickstart_app_startup', '13'); 401 }, 402 }, 403 { 404 title: 'Scheduling analysis抓取和展示说明', 405 icon: '', 406 clickHandler: function (item: MenuItem): void { 407 that.handleMemoryMenuItemClick(that, 'scheduling_record', 'quickstart_schedulinganalysis', '14'); 408 }, 409 }, 410 ], 411 }; 412 } 413 414 private setupFileMenu(that: this): MenuGroup { 415 return { 416 collapsed: false, 417 title: '文件', 418 describe: '', 419 second: true, 420 icon: 'caret-down', 421 children: [ 422 { 423 title: 'FileSystem抓取和展示说明', 424 icon: '', 425 clickHandler: function (item: MenuItem): void { 426 that.handleMemoryMenuItemClick(that, 'file_system', 'quickstart_filesystem', '15'); 427 }, 428 }, 429 { 430 title: 'Bio抓取和展示说明', 431 icon: '', 432 clickHandler: function (item: MenuItem): void { 433 that.handleMemoryMenuItemClick(that, 'bio', 'quickstart_bio', '16'); 434 }, 435 }, 436 ], 437 }; 438 } 439 440 private setupDatabaseMenu(that: this): MenuItem { 441 return { 442 title: 'TraceStreamer数据库说明', 443 icon: '', 444 clickHandler: function (item: MenuItem): void { 445 that.handleMemoryMenuItemClick(that, 'trace_streamer_explain', 'des_tables', '26'); 446 }, 447 }; 448 } 449 450 private setupCompileMenu(that: this): MenuItem { 451 return { 452 title: '编译Trace_streamer', 453 icon: '', 454 clickHandler: function (item: MenuItem): void { 455 that.handleMemoryMenuItemClick(that, 'trace_streamer_compile', 'compile_trace_streamer'); 456 }, 457 }; 458 } 459 460 private setupAnalysisMenu(that: this): MenuItem { 461 return { 462 title: 'TraceStreamer 解析数据状态表', 463 icon: '', 464 clickHandler: function (item: MenuItem): void { 465 that.handleMemoryMenuItemClick(that, 'trace_streamer_des', 'des_stat'); 466 }, 467 }; 468 } 469 470 private setupSmartPerfMenu(that: this): MenuItem { 471 return { 472 title: 'SmartPerf 编译指导', 473 icon: '', 474 clickHandler: function (item: MenuItem): void { 475 that.handleMemoryMenuItemClick(that, 'smartperf_guide', 'quickstart_smartperflinux_compile_guide'); 476 }, 477 }; 478 } 479 480 private setupEventListMenu(that: this): MenuItem { 481 return { 482 title: 'TraceStreamer支持解析事件列表', 483 icon: '', 484 clickHandler: function (item: MenuItem): void { 485 that.handleMemoryMenuItemClick(that, 'support_event', 'des_support_event'); 486 }, 487 }; 488 } 489 490 private setupToolDescriptionMenu(that: this): MenuItem { 491 return { 492 title: 'trace_streamer工具说明', 493 icon: '', 494 clickHandler: function (item: MenuItem): void { 495 that.handleMemoryMenuItemClick(that, 'quickstart_trace_streamer', 'quickstart_trace_streamer'); 496 }, 497 }; 498 } 499 500 private setupBinderMenu(that: this): MenuItem { 501 return { 502 title: 'binder事件上下文如何关联', 503 icon: '', 504 clickHandler: function (item: MenuItem): void { 505 that.handleMemoryMenuItemClick(that, 'binder', 'des_binder'); 506 }, 507 }; 508 } 509 510 private setupWakeUpMenu(that: this): MenuItem { 511 return { 512 title: 'wakeup唤醒说明', 513 icon: '', 514 clickHandler: function (item: MenuItem): void { 515 that.handleMemoryMenuItemClick(that, 'wakeup', 'des_wakup'); 516 }, 517 }; 518 } 519 520 initHtml(): string { 521 return ` 522 <style> 523 .sp-help-vessel { 524 min-height: 100%; 525 display: grid; 526 grid-template-columns: 1fr; 527 grid-template-rows:1fr; 528 background-color: var(--dark-background5,#F6F6F6); 529 } 530 :host{ 531 width: 100%; 532 display: block; 533 height: 100%; 534 background-color: var(--dark-background5,#F6F6F6); 535 } 536 .body{ 537 width: 99%; 538 margin-left: 15px; 539 display: grid; 540 grid-template-columns: min-content 1fr; 541 border-radius: 16px 16px 16px 16px; 542 } 543 544 .content{ 545 border-style: none none none solid; 546 border-width: 1px; 547 border-color: rgba(166,164,164,0.2); 548 border-radius: 0px 16px 16px 0px; 549 padding-left:15px; 550 display: flex; 551 overflow-y: hidden; 552 box-sizing: border-box; 553 } 554 #navbar-container { 555 border-left: 5px solid #ecb829; 556 } 557 #navbar-container ul { 558 list-style-type: none; 559 width:100%; 560 margin: 0; 561 padding: 0; 562 } 563 #navbar-container ul li { 564 position: relative; 565 width:100%; 566 height:30px; 567 line-height:30px; 568 text-align: left; 569 padding: 0 10px; 570 box-sizing: border-box; 571 border: none; 572 margin: 0; 573 } 574 #navbar-container ul li a { 575 width:100%; 576 height:100%; 577 color: black; 578 font-family: Helvetica; 579 font-size: 14px; 580 text-decoration: none; 581 display: block; 582 padding: 0; 583 border: none; 584 white-space: nowrap; 585 overflow: hidden; 586 text-overflow: ellipsis; 587 } 588 #navbar-container ul li:hover, 589 #navbar-container ul li:focus { 590 color: #ecb829; 591 cursor: pointer; 592 } 593 #navbar-container ul li:hover a, 594 #navbar-container ul li:focus a { 595 color: #ecb829; 596 } 597 #navbar-container ul li .tooltiptext { 598 position: absolute; 599 bottom: 0; 600 left: 50%; 601 transform: translateX(-50%); 602 visibility: hidden; 603 width: 100%; 604 background-color: #ecb829; 605 color: #fff; 606 font-family: Helvetica; 607 font-size: 14px; 608 text-align: center; 609 border-radius: 6px; 610 padding: 5px 5px; 611 margin-left:5px; 612 position: absolute; 613 z-index: 1; 614 opacity: 0; 615 transition: opacity 0.3s; 616 margin-bottom: 40px; 617 &::after { 618 content: ''; 619 position: absolute; 620 bottom: -10px; 621 left: 50%; 622 margin-left: -10px; 623 width: 0; 624 height: 0; 625 border-style: solid; 626 border-width: 10px 10px 0 10px; 627 border-color: #ecb829 transparent transparent transparent; 628 } 629 } 630 #navbar-container ul li.tooltip:hover .tooltiptext { 631 visibility: visible; 632 opacity: 1; 633 } 634 #navbar-container ul li.active, #navbar-container ul li.active a { 635 color: #ecb829; 636 } 637 638 </style> 639 <div class="sp-help-vessel"> 640 <div class="body"> 641 <lit-main-menu id="main-menu" class="menugroup" data=''></lit-main-menu> 642 <div id="app-content" class="content"> 643 <div id="help-file" style="width:100%;overflow-y: hidden;"></div> 644 <nav id="navbar-container" style="position:fixed;top:80px;left:79%;width:18%;"></nav> 645 <div class="back" style="position:fixed;top:80px;left:98%;width:2%;"> 646 <lit-icon id="back-to-top" name="vertical-align-top" style="font-weight: bold;cursor: pointer;" size="20"> 647 </lit-icon> 648 </div> 649 </div> 650 </div> 651 </div> 652 </div> 653 `; 654 } 655} 656