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 { element } from '../BaseElement'; 17import { LitTabpane } from './lit-tabpane'; 18import { SpStatisticsHttpUtil } from '../../statistics/util/SpStatisticsHttpUtil'; 19import { LitTabsHtml } from './lit-tabs.html'; 20 21@element('lit-tabs') 22export class LitTabs extends HTMLElement { 23 private tabPos: unknown; 24 private nav: HTMLDivElement | undefined | null; 25 private line: HTMLDivElement | undefined | null; 26 private slots: HTMLSlotElement | undefined | null; 27 28 constructor() { 29 super(); 30 const shadowRoot = this.attachShadow({ mode: 'open' }); 31 shadowRoot.innerHTML = LitTabsHtml; 32 } 33 34 static get observedAttributes(): string[] { 35 return ['activekey', 'mode', 'position']; 36 } 37 38 get position(): string { 39 return this.getAttribute('position') || 'top'; 40 } 41 42 set position(value) { 43 this.setAttribute('position', value); 44 } 45 46 get mode(): string { 47 return this.getAttribute('mode') || 'flat'; 48 } 49 50 set mode(value) { 51 this.setAttribute('mode', value); 52 } 53 54 get activekey(): string { 55 return this.getAttribute('activekey') || ''; 56 } 57 58 set activekey(value: string) { 59 this.setAttribute('activekey', value); 60 } 61 62 set onTabClick(fn: unknown) { 63 //@ts-ignore 64 this.addEventListener('onTabClick', fn); 65 } 66 67 updateLabel(key: string, value: string): void { 68 if (this.nav) { 69 let item = this.nav.querySelector(`.nav-item[data-key='${key}']`); 70 if (item) { 71 item.querySelector<HTMLSpanElement>('span')!.innerHTML = value; 72 this.initTabPos(); 73 } 74 } 75 } 76 77 updateDisabled(key: string, value: string): void { 78 if (this.nav) { 79 let item = this.nav.querySelector(`.nav-item[data-key='${key}']`); 80 if (item) { 81 if (value) { 82 item.setAttribute('data-disabled', ''); 83 } else { 84 item.removeAttribute('data-disabled'); 85 } 86 this.initTabPos(); 87 } 88 } 89 } 90 91 updateCloseable(key: string, value: string): void { 92 if (this.nav) { 93 let item = this.nav.querySelector(`.nav-item[data-key='${key}']`); 94 if (item) { 95 if (value) { 96 item.setAttribute('data-closeable', ''); 97 } else { 98 item.removeAttribute('data-closeable'); 99 } 100 this.initTabPos(); 101 } 102 } 103 } 104 105 updateHidden(key: string, value: string): void { 106 if (this.nav) { 107 let item = this.nav.querySelector(`.nav-item[data-key='${key}']`); 108 if (item) { 109 if (value === 'true') { 110 item.setAttribute('data-hidden', ''); 111 } else { 112 item.removeAttribute('data-hidden'); 113 } 114 this.initTabPos(); 115 } 116 } 117 } 118 119 initTabPos(): void { 120 const items = this.nav!.querySelectorAll<HTMLDivElement>('.nav-item'); 121 Array.from(items).forEach((a, index) => { 122 // @ts-ignore 123 this.tabPos[a.dataset.key] = { 124 index: index, 125 width: a.offsetWidth, 126 height: a.offsetHeight, 127 left: a.offsetLeft, 128 top: a.offsetTop, 129 label: a.textContent, 130 }; 131 }); 132 if (this.activekey) { 133 if (this.position.startsWith('left')) { 134 this.line?.setAttribute( 135 'style', //@ts-ignore 136 `height:${this.tabPos[this.activekey].height}px;transform:translate(100%,${ 137 //@ts-ignore 138 this.tabPos[this.activekey].top 139 }px)` 140 ); 141 } else if (this.position.startsWith('top')) { 142 //@ts-ignore 143 if (this.tabPos[this.activekey]) { 144 this.line?.setAttribute( 145 'style', //@ts-ignore 146 `width:${this.tabPos[this.activekey].width}px;transform:translate(${ 147 //@ts-ignore 148 this.tabPos[this.activekey].left 149 }px,100%)` 150 ); 151 } 152 } else if (this.position.startsWith('right')) { 153 this.line?.setAttribute( 154 'style', //@ts-ignore 155 `height:${this.tabPos[this.activekey].height}px;transform:translate(-100%,${ 156 //@ts-ignore 157 this.tabPos[this.activekey].top 158 }px)` 159 ); 160 } else if (this.position.startsWith('bottom')) { 161 this.line?.setAttribute( 162 'style', //@ts-ignore 163 `width:${this.tabPos[this.activekey].width}px;transform:translate(${this.tabPos[this.activekey].left}px,100%)` 164 ); 165 } 166 } 167 } 168 169 connectedCallback(): void { 170 this.tabPos = {}; 171 this.nav = this.shadowRoot?.querySelector('#nav'); 172 this.line = this.shadowRoot?.querySelector('#tab-line'); 173 this.slots = this.shadowRoot?.querySelector('#slot'); 174 this.slots?.addEventListener('slotchange', () => { 175 const elements: Element[] | undefined = this.slots?.assignedElements(); 176 let panes = this.querySelectorAll<LitTabpane>('lit-tabpane'); 177 if (this.activekey) { 178 panes.forEach((a) => { 179 if (a.key === this.activekey) { 180 a.style.display = 'block'; 181 } else { 182 a.style.display = 'none'; 183 } 184 }); 185 } else { 186 panes.forEach((a, index) => { 187 if (index === 0) { 188 a.style.display = 'block'; 189 this.activekey = a.key || ''; 190 } else { 191 a.style.display = 'none'; 192 } 193 }); 194 } 195 this.setItemNode(elements); 196 }); 197 this.nav!.onclick = (e): void => { 198 if ((e.target! as HTMLElement).closest('div')!.hasAttribute('data-disabled')) { 199 return; 200 } 201 let key = (e.target! as HTMLElement).closest('div')!.dataset.key; 202 if (key) { 203 this.activeByKey(key); 204 } 205 let label = (e.target! as HTMLElement).closest('div')!.querySelector('span')!.textContent; 206 this.dispatchEvent( 207 new CustomEvent('onTabClick', { 208 detail: { key: key, tab: label }, 209 }) 210 ); 211 }; 212 213 new ResizeObserver((entries) => { 214 let filling = this.shadowRoot!.querySelector<HTMLDivElement>('#tab-filling'); 215 216 this.shadowRoot!.querySelector<HTMLDivElement>('.tab-nav-vessel')!.style.height = filling!.offsetWidth + 'px'; 217 }).observe(this.shadowRoot!.querySelector('#tab-filling')!); 218 } 219 220 setItemNode(elements: Element[] | undefined): void { 221 let navHtml: string = ''; 222 elements 223 ?.map((it) => it as LitTabpane) 224 .forEach((a) => { 225 if (a.disabled) { 226 navHtml += `<div class="nav-item" data-key="${a.key}" data-disabled ${a.closeable ? 'data-closeable' : ''}> 227 ${a.icon ? `<lit-icon name='${a.icon}'></lit-icon>` : ''} 228 <span>${a.tab}</span> 229 <lit-icon class="close-icon" name='close' size="16"></lit-icon><div class="no-close-icon" style="margin-right: 12px"></div> 230 </div>`; 231 } else if (a.hidden) { 232 navHtml += `<div class="nav-item" data-key="${a.key}" data-hidden ${a.closeable ? 'data-closeable' : ''}> 233 ${a.icon ? `<lit-icon name='${a.icon}'></lit-icon>` : ''} 234 <span>${a.tab}</span> 235 <lit-icon class="close-icon" name='close' size="16"></lit-icon><div class="no-close-icon" style="margin-right: 12px"></div> 236 </div>`; 237 } else { 238 if (a.key === this.activekey) { 239 navHtml += `<div class="nav-item" data-key="${a.key}" data-selected ${a.closeable ? 'data-closeable' : ''}> 240 ${a.icon ? `<lit-icon name='${a.icon}'></lit-icon>` : ''} 241 <span>${a.tab}</span> 242 <lit-icon class="close-icon" name='close' size="16"></lit-icon><div class="no-close-icon" style="margin-right: 12px"></div> 243 </div>`; 244 } else { 245 navHtml += `<div class="nav-item" data-key="${a.key}" ${a.closeable ? 'data-closeable' : ''}> 246 ${a.icon ? `<lit-icon name='${a.icon}'></lit-icon>` : ''} 247 <span>${a.tab}</span> 248 <lit-icon class="close-icon" name='close' size="16"></lit-icon><div class="no-close-icon" style="margin-right: 12px"></div> 249 </div>`; 250 } 251 } 252 }); 253 this.nav!.innerHTML = navHtml; 254 this.initTabPos(); 255 this.nav!.querySelectorAll<HTMLElement>('.close-icon').forEach((a) => { 256 a.onclick = (e): void => { 257 e.stopPropagation(); 258 const closeKey = (e.target! as HTMLElement).parentElement!.dataset.key; 259 this.dispatchEvent( 260 new CustomEvent('close-handler', { 261 detail: { key: closeKey }, 262 composed: true, 263 }) 264 ); 265 }; 266 }); 267 } 268 269 activeByKey(key: string, isValid: boolean = true): void { 270 if (key === null || key === undefined) { 271 return; 272 } //如果没有key 不做相应 273 this.nav!.querySelectorAll('.nav-item').forEach((a) => { 274 if (a.querySelector('span')?.innerText === 'Comparison') { 275 a.setAttribute('id', 'nav-comparison'); 276 } 277 if (a.getAttribute('data-key') === key) { 278 a.setAttribute('data-selected', 'true'); 279 this.byKeyIsValid(isValid, a); 280 } else { 281 a.removeAttribute('data-selected'); 282 } 283 }); 284 let tbp = this.querySelector(`lit-tabpane[key='${key}']`); 285 let panes = this.querySelectorAll<LitTabpane>('lit-tabpane'); 286 panes.forEach((a) => { 287 if (a.key === key) { 288 a.style.display = 'block'; 289 this.activekey = a.key; 290 this.initTabPos(); 291 } else { 292 a.style.display = 'none'; 293 } 294 }); 295 } 296 297 byKeyIsValid(isValid: boolean, a: Element): void { 298 if (isValid) { 299 let span = a.querySelector('span') as HTMLSpanElement; 300 let title = span.innerText; 301 let rowType = document 302 .querySelector<HTMLElement>('sp-application')! 303 .shadowRoot?.querySelector<HTMLElement>('sp-system-trace')! 304 .getAttribute('clickRow'); 305 if (title === 'Counters' || title === 'Thread States') { 306 title += `(${rowType})`; 307 } 308 if (title === 'Analysis') { 309 let rowId = document 310 .querySelector<HTMLElement>('sp-application')! 311 .shadowRoot?.querySelector<HTMLElement>('sp-system-trace')! 312 .getAttribute('rowId'); 313 if (rowId!.indexOf('DiskIOLatency') > -1) { 314 title += '(disk-io)'; 315 } else if (rowId!.indexOf('VirtualMemory') > -1) { 316 title += '(virtual-memory-cell)'; 317 } else { 318 title += `(${rowType})`; 319 } 320 } 321 if (title === 'Slices' || title === 'Current Selection') { 322 let rowName = document 323 .querySelector<HTMLElement>('sp-application')! 324 .shadowRoot?.querySelector<HTMLElement>('sp-system-trace')! 325 .getAttribute('rowName'); 326 if (rowName && rowName!.indexOf('deliverInputEvent') > -1) { 327 title += '(deliverInputEvent)'; 328 } else { 329 let rowType = document 330 .querySelector<HTMLElement>('sp-application')! 331 .shadowRoot?.querySelector<HTMLElement>('sp-system-trace')! 332 .getAttribute('clickRow'); 333 title += `(${rowType})`; 334 } 335 } 336 SpStatisticsHttpUtil.addOrdinaryVisitAction({ 337 event: title, 338 action: 'trace_tab', 339 }); 340 } 341 } 342 343 activePane(key: string): boolean { 344 if (key === null || key === undefined) { 345 return false; 346 } 347 let tbp = this.querySelector(`lit-tabpane[key='${key}']`); 348 if (tbp) { 349 this.activeByKey(key); 350 return true; 351 } else { 352 return false; 353 } 354 } 355 356 disconnectedCallback(): void {} 357 358 adoptedCallback(): void {} 359 360 attributeChangedCallback(name: string, oldValue: string, newValue: string): void { 361 if (name === 'activekey' && this.nav && oldValue !== newValue && newValue !== '') { 362 this.activeByKey(newValue, false); 363 } 364 } 365} 366