• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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