xref: /aosp_15_r20/external/perfetto/ui/src/components/widgets/treetable.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1*6dbdd20aSAndroid Build Coastguard Worker// Copyright (C) 2023 The Android Open Source Project
2*6dbdd20aSAndroid Build Coastguard Worker//
3*6dbdd20aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License");
4*6dbdd20aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License.
5*6dbdd20aSAndroid Build Coastguard Worker// You may obtain a copy of the License at
6*6dbdd20aSAndroid Build Coastguard Worker//
7*6dbdd20aSAndroid Build Coastguard Worker//      http://www.apache.org/licenses/LICENSE-2.0
8*6dbdd20aSAndroid Build Coastguard Worker//
9*6dbdd20aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*6dbdd20aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS,
11*6dbdd20aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*6dbdd20aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and
13*6dbdd20aSAndroid Build Coastguard Worker// limitations under the License.
14*6dbdd20aSAndroid Build Coastguard Worker
15*6dbdd20aSAndroid Build Coastguard Workerimport m from 'mithril';
16*6dbdd20aSAndroid Build Coastguard Workerimport {classNames} from '../../base/classnames';
17*6dbdd20aSAndroid Build Coastguard Workerimport {raf} from '../../core/raf_scheduler';
18*6dbdd20aSAndroid Build Coastguard Worker
19*6dbdd20aSAndroid Build Coastguard Workerinterface ColumnDescriptor<T> {
20*6dbdd20aSAndroid Build Coastguard Worker  name: string;
21*6dbdd20aSAndroid Build Coastguard Worker  getData: (row: T) => string;
22*6dbdd20aSAndroid Build Coastguard Worker}
23*6dbdd20aSAndroid Build Coastguard Worker
24*6dbdd20aSAndroid Build Coastguard Workerexport interface TreeTableAttrs<T> {
25*6dbdd20aSAndroid Build Coastguard Worker  columns: ColumnDescriptor<T>[];
26*6dbdd20aSAndroid Build Coastguard Worker  getChildren: (row: T) => T[] | undefined;
27*6dbdd20aSAndroid Build Coastguard Worker  rows: T[];
28*6dbdd20aSAndroid Build Coastguard Worker}
29*6dbdd20aSAndroid Build Coastguard Worker
30*6dbdd20aSAndroid Build Coastguard Workerexport class TreeTable<T> implements m.ClassComponent<TreeTableAttrs<T>> {
31*6dbdd20aSAndroid Build Coastguard Worker  private collapsedPaths = new Set<string>();
32*6dbdd20aSAndroid Build Coastguard Worker
33*6dbdd20aSAndroid Build Coastguard Worker  view({attrs}: m.Vnode<TreeTableAttrs<T>, this>): void | m.Children {
34*6dbdd20aSAndroid Build Coastguard Worker    const {columns, rows} = attrs;
35*6dbdd20aSAndroid Build Coastguard Worker    const headers = columns.map(({name}) => m('th', name));
36*6dbdd20aSAndroid Build Coastguard Worker    const renderedRows = this.renderRows(rows, 0, attrs, []);
37*6dbdd20aSAndroid Build Coastguard Worker    return m(
38*6dbdd20aSAndroid Build Coastguard Worker      'table.pf-treetable',
39*6dbdd20aSAndroid Build Coastguard Worker      m('thead', m('tr', headers)),
40*6dbdd20aSAndroid Build Coastguard Worker      m('tbody', renderedRows),
41*6dbdd20aSAndroid Build Coastguard Worker    );
42*6dbdd20aSAndroid Build Coastguard Worker  }
43*6dbdd20aSAndroid Build Coastguard Worker
44*6dbdd20aSAndroid Build Coastguard Worker  private renderRows(
45*6dbdd20aSAndroid Build Coastguard Worker    rows: T[],
46*6dbdd20aSAndroid Build Coastguard Worker    indentLevel: number,
47*6dbdd20aSAndroid Build Coastguard Worker    attrs: TreeTableAttrs<T>,
48*6dbdd20aSAndroid Build Coastguard Worker    path: string[],
49*6dbdd20aSAndroid Build Coastguard Worker  ): m.Children {
50*6dbdd20aSAndroid Build Coastguard Worker    const {columns, getChildren} = attrs;
51*6dbdd20aSAndroid Build Coastguard Worker    const renderedRows: m.Children = [];
52*6dbdd20aSAndroid Build Coastguard Worker    for (const row of rows) {
53*6dbdd20aSAndroid Build Coastguard Worker      const childRows = getChildren(row);
54*6dbdd20aSAndroid Build Coastguard Worker      const key = this.keyForRow(row, attrs);
55*6dbdd20aSAndroid Build Coastguard Worker      const thisPath = path.concat([key]);
56*6dbdd20aSAndroid Build Coastguard Worker      const hasChildren = childRows && childRows.length > 0;
57*6dbdd20aSAndroid Build Coastguard Worker      const cols = columns.map(({getData}, index) => {
58*6dbdd20aSAndroid Build Coastguard Worker        const classes = classNames(
59*6dbdd20aSAndroid Build Coastguard Worker          hasChildren && 'pf-treetable-node',
60*6dbdd20aSAndroid Build Coastguard Worker          this.isCollapsed(thisPath) && 'pf-collapsed',
61*6dbdd20aSAndroid Build Coastguard Worker        );
62*6dbdd20aSAndroid Build Coastguard Worker        if (index === 0) {
63*6dbdd20aSAndroid Build Coastguard Worker          const style = {
64*6dbdd20aSAndroid Build Coastguard Worker            '--indentation-level': indentLevel,
65*6dbdd20aSAndroid Build Coastguard Worker          };
66*6dbdd20aSAndroid Build Coastguard Worker          return m(
67*6dbdd20aSAndroid Build Coastguard Worker            'td',
68*6dbdd20aSAndroid Build Coastguard Worker            {style, class: classNames(classes, 'pf-treetable-maincol')},
69*6dbdd20aSAndroid Build Coastguard Worker            m('.pf-treetable-gutter', {
70*6dbdd20aSAndroid Build Coastguard Worker              onclick: () => {
71*6dbdd20aSAndroid Build Coastguard Worker                if (this.isCollapsed(thisPath)) {
72*6dbdd20aSAndroid Build Coastguard Worker                  this.expandPath(thisPath);
73*6dbdd20aSAndroid Build Coastguard Worker                } else {
74*6dbdd20aSAndroid Build Coastguard Worker                  this.collapsePath(thisPath);
75*6dbdd20aSAndroid Build Coastguard Worker                }
76*6dbdd20aSAndroid Build Coastguard Worker                raf.scheduleFullRedraw();
77*6dbdd20aSAndroid Build Coastguard Worker              },
78*6dbdd20aSAndroid Build Coastguard Worker            }),
79*6dbdd20aSAndroid Build Coastguard Worker            getData(row),
80*6dbdd20aSAndroid Build Coastguard Worker          );
81*6dbdd20aSAndroid Build Coastguard Worker        } else {
82*6dbdd20aSAndroid Build Coastguard Worker          const style = {
83*6dbdd20aSAndroid Build Coastguard Worker            '--indentation-level': 0,
84*6dbdd20aSAndroid Build Coastguard Worker          };
85*6dbdd20aSAndroid Build Coastguard Worker          return m('td', {style}, getData(row));
86*6dbdd20aSAndroid Build Coastguard Worker        }
87*6dbdd20aSAndroid Build Coastguard Worker      });
88*6dbdd20aSAndroid Build Coastguard Worker      renderedRows.push(m('tr', cols));
89*6dbdd20aSAndroid Build Coastguard Worker      if (childRows && !this.isCollapsed(thisPath)) {
90*6dbdd20aSAndroid Build Coastguard Worker        renderedRows.push(
91*6dbdd20aSAndroid Build Coastguard Worker          this.renderRows(childRows, indentLevel + 1, attrs, thisPath),
92*6dbdd20aSAndroid Build Coastguard Worker        );
93*6dbdd20aSAndroid Build Coastguard Worker      }
94*6dbdd20aSAndroid Build Coastguard Worker    }
95*6dbdd20aSAndroid Build Coastguard Worker    return renderedRows;
96*6dbdd20aSAndroid Build Coastguard Worker  }
97*6dbdd20aSAndroid Build Coastguard Worker
98*6dbdd20aSAndroid Build Coastguard Worker  collapsePath(path: string[]) {
99*6dbdd20aSAndroid Build Coastguard Worker    const pathStr = path.join('/');
100*6dbdd20aSAndroid Build Coastguard Worker    this.collapsedPaths.add(pathStr);
101*6dbdd20aSAndroid Build Coastguard Worker  }
102*6dbdd20aSAndroid Build Coastguard Worker
103*6dbdd20aSAndroid Build Coastguard Worker  expandPath(path: string[]) {
104*6dbdd20aSAndroid Build Coastguard Worker    const pathStr = path.join('/');
105*6dbdd20aSAndroid Build Coastguard Worker    this.collapsedPaths.delete(pathStr);
106*6dbdd20aSAndroid Build Coastguard Worker  }
107*6dbdd20aSAndroid Build Coastguard Worker
108*6dbdd20aSAndroid Build Coastguard Worker  isCollapsed(path: string[]) {
109*6dbdd20aSAndroid Build Coastguard Worker    const pathStr = path.join('/');
110*6dbdd20aSAndroid Build Coastguard Worker    return this.collapsedPaths.has(pathStr);
111*6dbdd20aSAndroid Build Coastguard Worker  }
112*6dbdd20aSAndroid Build Coastguard Worker
113*6dbdd20aSAndroid Build Coastguard Worker  keyForRow(row: T, attrs: TreeTableAttrs<T>): string {
114*6dbdd20aSAndroid Build Coastguard Worker    const {columns} = attrs;
115*6dbdd20aSAndroid Build Coastguard Worker    return columns[0].getData(row);
116*6dbdd20aSAndroid Build Coastguard Worker  }
117*6dbdd20aSAndroid Build Coastguard Worker}
118