• 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: 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