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