• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2023 The Android Open Source Project
2//
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
15import m from 'mithril';
16import {classNames} from '../../base/classnames';
17
18interface ColumnDescriptor<T> {
19  name: string;
20  getData: (row: T) => string;
21}
22
23export interface TreeTableAttrs<T> {
24  columns: ColumnDescriptor<T>[];
25  getChildren: (row: T) => T[] | undefined;
26  rows: T[];
27}
28
29export class TreeTable<T> implements m.ClassComponent<TreeTableAttrs<T>> {
30  private collapsedPaths = new Set<string>();
31
32  view({attrs}: m.Vnode<TreeTableAttrs<T>, this>): void | m.Children {
33    const {columns, rows} = attrs;
34    const headers = columns.map(({name}) => m('th', name));
35    const renderedRows = this.renderRows(rows, 0, attrs, []);
36    return m(
37      'table.pf-treetable',
38      m('thead', m('tr', headers)),
39      m('tbody', renderedRows),
40    );
41  }
42
43  private renderRows(
44    rows: T[],
45    indentLevel: number,
46    attrs: TreeTableAttrs<T>,
47    path: string[],
48  ): m.Children {
49    const {columns, getChildren} = attrs;
50    const renderedRows: m.Children = [];
51    for (const row of rows) {
52      const childRows = getChildren(row);
53      const key = this.keyForRow(row, attrs);
54      const thisPath = path.concat([key]);
55      const hasChildren = childRows && childRows.length > 0;
56      const cols = columns.map(({getData}, index) => {
57        const classes = classNames(
58          hasChildren && 'pf-treetable-node',
59          this.isCollapsed(thisPath) && 'pf-collapsed',
60        );
61        if (index === 0) {
62          const style = {
63            '--indentation-level': indentLevel,
64          };
65          return m(
66            'td',
67            {style, class: classNames(classes, 'pf-treetable-maincol')},
68            m('.pf-treetable-gutter', {
69              onclick: () => {
70                if (this.isCollapsed(thisPath)) {
71                  this.expandPath(thisPath);
72                } else {
73                  this.collapsePath(thisPath);
74                }
75              },
76            }),
77            getData(row),
78          );
79        } else {
80          const style = {
81            '--indentation-level': 0,
82          };
83          return m('td', {style}, getData(row));
84        }
85      });
86      renderedRows.push(m('tr', cols));
87      if (childRows && !this.isCollapsed(thisPath)) {
88        renderedRows.push(
89          this.renderRows(childRows, indentLevel + 1, attrs, thisPath),
90        );
91      }
92    }
93    return renderedRows;
94  }
95
96  collapsePath(path: string[]) {
97    const pathStr = path.join('/');
98    this.collapsedPaths.add(pathStr);
99  }
100
101  expandPath(path: string[]) {
102    const pathStr = path.join('/');
103    this.collapsedPaths.delete(pathStr);
104  }
105
106  isCollapsed(path: string[]) {
107    const pathStr = path.join('/');
108    return this.collapsedPaths.has(pathStr);
109  }
110
111  keyForRow(row: T, attrs: TreeTableAttrs<T>): string {
112    const {columns} = attrs;
113    return columns[0].getData(row);
114  }
115}
116