• 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.js';
17import { LitTabpane } from './lit-tabpane.js';
18import { SpStatisticsHttpUtil } from '../../statistics/util/SpStatisticsHttpUtil.js';
19
20@element('lit-tabs')
21export class LitTabs extends HTMLElement {
22  private tabPos: any;
23  private nav: HTMLDivElement | undefined | null;
24  private line: HTMLDivElement | undefined | null;
25  private slots: HTMLSlotElement | undefined | null;
26
27  constructor() {
28    super();
29    const shadowRoot = this.attachShadow({ mode: 'open' });
30    shadowRoot.innerHTML = `
31        <style>
32        :host{
33            display: block;
34            text-align: unset;
35            color: var(--dark-color1,#252525);
36            background-color: var(--dark-background,#FFFFFF);
37            box-shadow: #00000033 0 0 10px ;
38        }
39        ::slotted(lit-tabpane){
40            box-sizing:border-box;
41            width:100%;
42            height:100%;
43            flex-shrink:0;
44            overflow: auto;
45        }
46        .nav-item{
47            display: inline-flex;
48            justify-content: center;
49            align-items: center;
50            /*padding: 6px 0px 6px 12px;*/
51            padding-left: 12px;
52            font-size: .9rem;
53            font-weight: normal;
54            cursor: pointer;
55            transition: all 0.3s;
56            flex-shrink: 0;
57        }
58        .nav-item lit-icon{
59            margin-right: 2px;
60            font-size: inherit;
61        }
62
63        .nav-item[data-disabled]{
64            pointer-events: all;
65            cursor: not-allowed;
66            color: #bfbfbf;
67        }
68        .nav-item[data-hidden]{
69            pointer-events: all;
70            cursor: not-allowed;
71            color: #bfbfbf;
72            display: none;
73        }
74
75        .tab-content{
76            display: block;
77            background-color: #fff;
78            flex:1;
79        }
80
81        /*
82         *   top  top-left top-center top-right
83         */
84        :host(:not([position])) .nav-root,
85        :host([position^='top']) .nav-root{
86            display: flex;
87            position: relative;
88            height: 38px;
89            z-index: auto;
90            /*justify-content: center;*/
91            /*align-items: center;*/
92        }
93        :host(:not([mode]):not([position])) .tab-line,/*移动的线条*/
94        :host([mode='flat'][position^='top']) .tab-line{
95            position:absolute;
96        }
97
98        :host(:not([position])) .tab-nav-container,
99        :host([position^='top']) .tab-nav-container{
100            display: flex;
101            /*position: relative;*/
102            /*flex-direction: column;*/
103            /*overflow-y: hidden;*/
104            /*overflow-x: auto;*/
105            /*overflow: -moz-scrollbars-none; */
106            /*-ms-overflow-style: none;*/
107            /*transition: all 0.3s;*/
108
109            position: absolute;
110            overflow: auto;
111            height: 850px;
112            transform: rotateZ(-90deg) rotateY(180deg);
113            transform-origin: left top;
114            overflow-x: hidden;
115            width: 38px;
116
117            cursor: row-resize;
118            user-select: none;
119        }
120        :host([position='top']) .tab-nav,
121        :host([position='top-left']) .tab-nav{
122            display: flex;
123            position: relative;
124            justify-content: flex-start;
125            cursor: row-resize;
126            user-select: none;
127            margin-top: 6px;
128            margin-left: 5px;
129
130            transform: translateY(-38px) rotateZ(90deg) rotateX(180deg) translateY(38px);
131            transform-origin: left bottom;
132            flex-wrap: nowrap;
133            height: 38px;
134        }
135        :host([position='top-center']) .tab-nav{
136            display: flex;
137            justify-content: center;
138        }
139        :host([position='top-right']) .tab-nav{
140            display: flex;
141            justify-content: flex-end;
142        }
143
144        :host([position^='top'][mode='card']) .nav-item{
145            border-top: 1px solid var(--dark-border1,#f0f0f0);
146            border-left: 1px solid var(--dark-border1,#f0f0f0);
147            border-right: 1px solid var(--dark-border1,#f0f0f0);
148            bottom: 0px;
149            margin-right: 2px;
150            position: relative;
151            height: 100%;
152        }
153        :host([position^='top']) .tab-nav-bg-line{
154            position: absolute;bottom: 0;height: 1px;
155            width: 100%
156        }
157        :host([position^='top'][mode='card']) .nav-item:not([data-selected]){
158            border-top-left-radius: 5px;
159            border-top-right-radius: 5px;
160            background-color: var(--dark-border1,#D8D8D8);
161            color: var(--dark-color1,#212121);
162        }
163        :host([position^='top'][mode='card']) .nav-item[data-selected]{
164            background-color: var(--dark-background,#ffffff);
165            bottom: 0px;
166            color: var(--dark-color1,#212121);
167            border-top: 1px solid var(--dark-border1,#bababa);
168            border-top-left-radius: 5px;
169            border-top-right-radius: 5px;
170            border-left: 1px solid var(--dark-border1,#bababa);
171            border-right: 1px solid var(--dark-border1,#bababa);
172        }
173        /*
174            bottom bottom-left bottom-center bottom-right
175        */
176        :host([position^='bottom']) .tab{
177            display: flex;
178            flex-direction: column-reverse;
179        }
180        :host([mode='flat'][position^='bottom']) .tab-line{
181            position:absolute;
182            top: -3px;
183            background-color: #42b983;
184            height: 2px;
185            transform: translateY(-100%);
186            transition: all 0.3s;
187        }
188        :host([position^='bottom']) .tab-nav-container{
189            display: flex;
190            position: relative;
191            flex-direction: column;
192            overflow-x: auto;
193            overflow-y: visible;
194            overflow: -moz-scrollbars-none;
195            -ms-overflow-style: none;
196            transition: all 0.3s;
197            flex: 1;
198            border-top: #f0f0f0 1px solid;
199        }
200        :host([position^='bottom']) .nav-root{
201            display: flex;
202            justify-content: center;
203            align-items: center;
204        }
205        :host([position='bottom']) .tab-nav,
206        :host([position='bottom-left']) .tab-nav{
207            display: flex;
208            position: relative;
209            justify-content: flex-start;
210        }
211        :host([position='bottom-center']) .tab-nav{
212            display: flex;
213            justify-content: center;
214        }
215        :host([position='bottom-right']) .tab-nav{
216            display: flex;
217            justify-content: flex-end;
218        }
219        :host([position^='bottom'][mode='card']) .nav-item{
220            border-top: 1px solid #ffffff;
221            border-left: 1px solid #f0f0f0;
222            border-right: 1px solid #f0f0f0;
223            border-bottom: 1px solid #f0f0f0;
224            top: -1px;
225            margin-right: 2px;
226            position: relative;
227        }
228        :host([position^='bottom']) .tab-nav-bg-line{
229            position: absolute;top: 0;height: 1px;background-color: #f0f0f0;width: 100%
230        }
231        :host([position^='bottom'][mode='card']) .nav-item:not([data-selected]){
232            background-color: #f5f5f5;
233            border-top: 1px solid #f0f0f0;
234        }
235        :host([position^='bottom'][mode='card']) .nav-item[data-selected]{
236            background-color: #ffffff;
237            border-top: 1px solid #fff;
238            top: -1px;
239        }
240        /*
241        left left-top left-center left-bottom
242        */
243        :host([position^='left']) .tab{
244            display: flex;
245            flex-direction: row;
246        }
247        :host([mode='flat'][position^='left']) .tab-line{
248            position:absolute;
249            right: 1px;
250            background-color: #42b983;
251            width: 3px;
252            transform: translateX(100%);
253            transition: all 0.3s;
254        }
255        :host([position^='left']) .tab-nav-container{
256            display: flex;
257            position: relative;
258            flex-direction: row;
259            overflow-x: auto;
260            overflow-y: visible;
261            overflow: -moz-scrollbars-none;
262            -ms-overflow-style: none;
263            transition: all 0.3s;
264            flex: 1;
265            border-right: #f0f0f0 1px solid;
266        }
267        :host([position^='left']) .nav-root{
268            display: flex;
269            flex-direction: column;
270            justify-content: center;
271            align-items: center;
272        }
273        :host([position='left']) .tab-nav,
274        :host([position='left-top']) .tab-nav{
275            display: flex;
276            position: relative;
277            flex-direction: column;
278            justify-content: flex-start;
279        }
280        :host([position='left-center']) .tab-nav{
281            display: flex;
282            position: relative;
283            flex-direction: column;
284            justify-content: center;
285        }
286        :host([position='left-bottom']) .tab-nav{
287            display: flex;
288            position: relative;
289            flex-direction: column;
290            justify-content: flex-end;
291        }
292        :host([position^='left'][mode='card']) .nav-item{
293            border-top: 1px solid #f0f0f0;
294            border-left: 1px solid #f0f0f0;
295            border-right: 1px solid #ffffff;
296            border-bottom: 1px solid #f0f0f0;
297            right: -1px;
298            margin-bottom: 2px;
299            position: relative;
300        }
301        :host([position^='left']) .tab-nav-bg-line{
302            position: absolute;right: 0;width: 1px;background-color: #f0f0f0;width: 100%
303        }
304        :host([position^='left'][mode='card']) .nav-item:not([data-selected]){
305            background-color: #f5f5f5;
306            border-right: 1px solid #f0f0f0;
307        }
308        :host([position^='left'][mode='card']) .nav-item[data-selected]{
309            background-color: #ffffff;
310            border-bottom: 1px solid #fff;
311            right: -1px;
312        }
313        /*
314        right right-top right-center right-bottom
315        */
316        :host([position^='right']) .tab{
317            display: flex;
318            flex-direction: row-reverse;
319        }
320        :host([mode='flat'][position^='right']) .tab-line{
321            position:absolute;
322            left: 1px;
323            background-color: #42b983;
324            width: 3px;
325            transform: translateX(-100%);
326            transition: all 0.3s;
327        }
328        :host([position^='right']) .tab-nav-container{
329            display: flex;
330            position: relative;
331            flex-direction: row-reverse;
332            overflow-x: auto;
333            overflow-y: visible;
334            overflow: -moz-scrollbars-none;
335            -ms-overflow-style: none;
336            transition: all 0.3s;
337            flex: 1;
338            border-left: #f0f0f0 1px solid;
339        }
340        :host([position^='right']) .nav-root{
341            display: flex;
342            flex-direction: column;
343            justify-content: center;
344            align-items: center;
345        }
346        :host([position='right']) .tab-nav,
347        :host([position='right-top']) .tab-nav{
348            display: flex;
349            position: relative;
350            flex-direction: column;
351            justify-content: flex-start;
352        }
353        :host([position='right-center']) .tab-nav{
354            display: flex;
355            position: relative;
356            flex-direction: column;
357            justify-content: center;
358        }
359        :host([position='right-bottom']) .tab-nav{
360            display: flex;
361            position: relative;
362            flex-direction: column;
363            justify-content: flex-end;
364        }
365        :host([position^='right'][mode='card']) .nav-item{
366            border-top: 1px solid #f0f0f0;
367            border-left: 1px solid #ffffff;
368            border-right: 1px solid #f0f0f0;
369            border-bottom: 1px solid #f0f0f0;
370            left: -1px;
371            margin-top: 2px;
372            position: relative;
373        }
374        :host([position^='right']) .tab-nav-bg-line{
375            position: absolute;left: 0;width: 1px;background-color: #f0f0f0;width: 100%
376        }
377        :host([position^='right'][mode='card']) .nav-item:not([data-selected]){
378            background-color: #f5f5f5;
379            border-left: 1px solid #f0f0f0;
380        }
381        :host([position^='right'][mode='card']) .nav-item[data-selected]{
382            background-color: #ffffff;
383            left: -1px;
384        }
385
386
387        .tab-nav-container::-webkit-scrollbar {
388            display: none;
389        }
390
391        .close-icon:hover{
392            color: #000;
393        }
394        .nav-item[data-closeable] .close-icon{
395            display: block;
396            padding: 2px 5px;
397            color: var(--dark-icon,#606060)
398        }
399        .nav-item[data-closeable] .no-close-icon{
400            display: none;
401        }
402        .nav-item:not([data-closeable]) .no-close-icon{
403            display: block;
404        }
405        .nav-item:not([data-closeable]) .close-icon{
406            display: none;
407        }
408
409        </style>
410        <style id="filter"></style>
411        <div class="tab" >
412            <div class="nav-root" style="background-color: var(--dark-background4,#f2f2f2);">
413                <slot name="left" style="flex:1"></slot>
414                <div class="tab-nav-container" >
415                    <div class="tab-nav-bg-line"></div>
416                    <div class="tab-nav" id="nav" ></div>
417                    <div class="tab-line" id="tab-line"></div>
418                </div>
419                <div id="tab-filling" style="flex: 1"></div>
420                <slot name="right" style="flex:1"></slot>
421            </div>
422            <div class="tab-content">
423                <slot id="slot">NEED CONTENT</slot>
424            </div>
425        </div>
426        `;
427  }
428
429  static get observedAttributes() {
430    return ['activekey', 'mode', 'position'];
431  }
432
433  get position() {
434    return this.getAttribute('position') || 'top';
435  }
436
437  set position(value) {
438    this.setAttribute('position', value);
439  }
440
441  get mode() {
442    return this.getAttribute('mode') || 'flat';
443  }
444
445  set mode(value) {
446    this.setAttribute('mode', value);
447  }
448
449  get activekey() {
450    return this.getAttribute('activekey') || '';
451  }
452
453  set activekey(value: string) {
454    this.setAttribute('activekey', value);
455  }
456
457  set onTabClick(fn: any) {
458    this.addEventListener('onTabClick', fn);
459  }
460
461  updateLabel(key: string, value: string) {
462    if (this.nav) {
463      let item = this.nav.querySelector(`.nav-item[data-key='${key}']`);
464      if (item) {
465        item.querySelector<HTMLSpanElement>('span')!.innerHTML = value;
466        this.initTabPos();
467      }
468    }
469  }
470
471  updateDisabled(key: string, value: string) {
472    if (this.nav) {
473      let item = this.nav.querySelector(`.nav-item[data-key='${key}']`);
474      if (item) {
475        if (value) {
476          item.setAttribute('data-disabled', '');
477        } else {
478          item.removeAttribute('data-disabled');
479        }
480        this.initTabPos();
481      }
482    }
483  }
484
485  updateCloseable(key: string, value: string) {
486    if (this.nav) {
487      let item = this.nav.querySelector(`.nav-item[data-key='${key}']`);
488      if (item) {
489        if (value) {
490          item.setAttribute('data-closeable', '');
491        } else {
492          item.removeAttribute('data-closeable');
493        }
494        this.initTabPos();
495      }
496    }
497  }
498
499  updateHidden(key: string, value: string) {
500    if (this.nav) {
501      let item = this.nav.querySelector(`.nav-item[data-key='${key}']`);
502      if (item) {
503        if (value === 'true') {
504          item.setAttribute('data-hidden', '');
505        } else {
506          item.removeAttribute('data-hidden');
507        }
508        this.initTabPos();
509      }
510    }
511  }
512
513  initTabPos() {
514    const items = this.nav!.querySelectorAll<HTMLDivElement>('.nav-item');
515    Array.from(items).forEach((a, index) => {
516      // @ts-ignore
517      this.tabPos[a.dataset.key] = {
518        index: index,
519        width: a.offsetWidth,
520        height: a.offsetHeight,
521        left: a.offsetLeft,
522        top: a.offsetTop,
523        label: a.textContent,
524      };
525    });
526    if (this.activekey) {
527      if (this.position.startsWith('left')) {
528        this.line?.setAttribute(
529          'style',
530          `height:${this.tabPos[this.activekey].height}px;transform:translate(100%,${
531            this.tabPos[this.activekey].top
532          }px)`
533        );
534      } else if (this.position.startsWith('top')) {
535        if (this.tabPos[this.activekey]) {
536          this.line?.setAttribute(
537            'style',
538            `width:${this.tabPos[this.activekey].width}px;transform:translate(${
539              this.tabPos[this.activekey].left
540            }px,100%)`
541          );
542        }
543      } else if (this.position.startsWith('right')) {
544        this.line?.setAttribute(
545          'style',
546          `height:${this.tabPos[this.activekey].height}px;transform:translate(-100%,${
547            this.tabPos[this.activekey].top
548          }px)`
549        );
550      } else if (this.position.startsWith('bottom')) {
551        this.line?.setAttribute(
552          'style',
553          `width:${this.tabPos[this.activekey].width}px;transform:translate(${this.tabPos[this.activekey].left}px,100%)`
554        );
555      }
556    }
557  }
558
559  connectedCallback() {
560    let that = this;
561    this.tabPos = {};
562    this.nav = this.shadowRoot?.querySelector('#nav');
563    this.line = this.shadowRoot?.querySelector('#tab-line');
564    this.slots = this.shadowRoot?.querySelector('#slot');
565    this.slots?.addEventListener('slotchange', () => {
566      const elements: Element[] | undefined = this.slots?.assignedElements();
567      let panes = this.querySelectorAll<LitTabpane>('lit-tabpane');
568      if (this.activekey) {
569        panes.forEach((a) => {
570          if (a.key === this.activekey) {
571            a.style.display = 'block';
572          } else {
573            a.style.display = 'none';
574          }
575        });
576      } else {
577        panes.forEach((a, index) => {
578          if (index === 0) {
579            a.style.display = 'block';
580            this.activekey = a.key || '';
581          } else {
582            a.style.display = 'none';
583          }
584        });
585      }
586      let navHtml = '';
587      elements
588        ?.map((it) => it as LitTabpane)
589        .forEach((a) => {
590          if (a.disabled) {
591            navHtml += `<div class="nav-item" data-key="${a.key}" data-disabled ${a.closeable ? 'data-closeable' : ''}>
592                    ${a.icon ? `<lit-icon name='${a.icon}'></lit-icon>` : ``}
593                    <span>${a.tab}</span>
594                    <lit-icon class="close-icon" name='close' size="16"></lit-icon><div class="no-close-icon" style="margin-right: 12px"></div>
595                    </div>`;
596          } else if (a.hidden) {
597            navHtml += `<div class="nav-item" data-key="${a.key}" data-hidden ${a.closeable ? 'data-closeable' : ''}>
598                    ${a.icon ? `<lit-icon name='${a.icon}'></lit-icon>` : ``}
599                    <span>${a.tab}</span>
600                    <lit-icon class="close-icon" name='close' size="16"></lit-icon><div class="no-close-icon" style="margin-right: 12px"></div>
601                    </div>`;
602          } else {
603            if (a.key === this.activekey) {
604              navHtml += `<div class="nav-item" data-key="${a.key}" data-selected ${
605                a.closeable ? 'data-closeable' : ''
606              }>
607                        ${a.icon ? `<lit-icon name='${a.icon}'></lit-icon>` : ``}
608                        <span>${a.tab}</span>
609                        <lit-icon class="close-icon" name='close' size="16"></lit-icon><div class="no-close-icon" style="margin-right: 12px"></div>
610                        </div>`;
611            } else {
612              navHtml += `<div class="nav-item" data-key="${a.key}" ${a.closeable ? 'data-closeable' : ''}>
613                            ${a.icon ? `<lit-icon name='${a.icon}'></lit-icon>` : ``}
614                            <span>${a.tab}</span>
615                            <lit-icon class="close-icon" name='close' size="16"></lit-icon><div class="no-close-icon" style="margin-right: 12px"></div>
616                            </div>`;
617            }
618          }
619        });
620      this.nav!.innerHTML = navHtml;
621      this.initTabPos();
622      this.nav!.querySelectorAll<HTMLElement>('.close-icon').forEach((a) => {
623        a.onclick = (e) => {
624          e.stopPropagation();
625          const closeKey = (e.target! as HTMLElement).parentElement!.dataset.key;
626          this.dispatchEvent(
627            new CustomEvent('close-handler', {
628              detail: { key: closeKey },
629              composed: true,
630            })
631          );
632        };
633      });
634    });
635    this.nav!.onclick = (e) => {
636      if ((e.target! as HTMLElement).closest('div')!.hasAttribute('data-disabled')) return;
637      let key = (e.target! as HTMLElement).closest('div')!.dataset.key;
638      if (key) {
639        this.activeByKey(key);
640      }
641      let label = (e.target! as HTMLElement).closest('div')!.querySelector('span')!.textContent;
642      this.dispatchEvent(
643        new CustomEvent('onTabClick', {
644          detail: { key: key, tab: label },
645        })
646      );
647    };
648
649    new ResizeObserver((entries) => {
650      let filling = this.shadowRoot!.querySelector<HTMLDivElement>('#tab-filling');
651
652      this.shadowRoot!.querySelector<HTMLDivElement>('.tab-nav-container')!.style.height = filling!.offsetWidth + 'px';
653    }).observe(this.shadowRoot!.querySelector('#tab-filling')!);
654  }
655
656  activeByKey(key: string, isValid: boolean = true) {
657    if (key === null || key === undefined) return; //如果没有key 不做相应
658    this.nav!.querySelectorAll('.nav-item').forEach((a) => {
659      if (a.querySelector('span')?.innerText === 'Comparison') {
660        a.setAttribute('id', 'nav-comparison');
661      }
662      if (a.getAttribute('data-key') === key) {
663        a.setAttribute('data-selected', 'true');
664        if (isValid) {
665          let span = a.querySelector('span') as HTMLSpanElement;
666          let title = span.innerText;
667          let rowType = document
668              .querySelector<HTMLElement>('sp-application')!
669              .shadowRoot?.querySelector<HTMLElement>('sp-system-trace')!
670              .getAttribute('clickRow');
671          if (title === 'Counters' || title === 'Thread States') {
672            title += `(${rowType})`;
673          }
674          if (title === 'Analysis') {
675            let rowId = document
676                .querySelector<HTMLElement>('sp-application')!
677                .shadowRoot?.querySelector<HTMLElement>('sp-system-trace')!
678                .getAttribute('rowId');
679            if (rowId!.indexOf('DiskIOLatency') > -1) {
680              title += '(disk-io)';
681            } else if (rowId!.indexOf('VirtualMemory') > -1) {
682              title += '(virtual-memory-cell)';
683            } else {
684              title += `(${rowType})`;
685            }
686          }
687          if (title === 'Slices' || title === 'Current Selection') {
688            let rowName = document
689              .querySelector<HTMLElement>('sp-application')!
690              .shadowRoot?.querySelector<HTMLElement>('sp-system-trace')!
691              .getAttribute('rowName');
692            if (rowName && rowName!.indexOf('deliverInputEvent') > -1) {
693              title += '(deliverInputEvent)';
694            } else {
695              let rowType = document
696                .querySelector<HTMLElement>('sp-application')!
697                .shadowRoot?.querySelector<HTMLElement>('sp-system-trace')!
698                .getAttribute('clickRow');
699              title += `(${rowType})`;
700            }
701          }
702          SpStatisticsHttpUtil.addOrdinaryVisitAction({
703            event: title,
704            action: 'trace_tab',
705          });
706        }
707      } else {
708        a.removeAttribute('data-selected');
709      }
710    });
711    let tbp = this.querySelector(`lit-tabpane[key='${key}']`);
712    let panes = this.querySelectorAll<LitTabpane>('lit-tabpane');
713    panes.forEach((a) => {
714      if (a.key === key) {
715        a.style.display = 'block';
716        this.activekey = a.key;
717        this.initTabPos();
718      } else {
719        a.style.display = 'none';
720      }
721    });
722  }
723
724  activePane(key: string) {
725    if (key === null || key === undefined) return false;
726    let tbp = this.querySelector(`lit-tabpane[key='${key}']`);
727    if (tbp) {
728      this.activeByKey(key);
729      return true;
730    } else {
731      return false;
732    }
733  }
734
735  disconnectedCallback() {}
736
737  adoptedCallback() {}
738
739  attributeChangedCallback(name: string, oldValue: string, newValue: string) {
740    if (name === 'activekey' && this.nav && oldValue !== newValue && newValue != '') {
741      this.activeByKey(newValue, false);
742    }
743  }
744}
745