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