• 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 size 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';
16
17import {Tree, TreeNode} from '../widgets/tree';
18
19import {PopupMenuButton, PopupMenuItem} from './popup_menu';
20
21// This file implements a component for rendering JSON-like values (with
22// customisation options like context menu and action buttons).
23//
24// It defines the common Value, StringValue, DictValue, ArrayValue types,
25// to be used as an interchangeable format between different components
26// and `renderValue` function to convert DictValue into vdom nodes.
27
28// Leaf (non-dict and non-array) value which can be displayed to the user
29// together with the rendering customisation parameters.
30type StringValue = {
31  kind: 'STRING';
32  value: string;
33} & StringValueParams;
34
35// Helper function to create a StringValue from string together with optional
36// parameters.
37export function value(value: string, params?: StringValueParams): StringValue {
38  return {
39    kind: 'STRING',
40    value,
41    ...params,
42  };
43}
44
45// Helper function to convert a potentially undefined value to StringValue or
46// null.
47export function maybeValue(
48  v?: string,
49  params?: StringValueParams,
50): StringValue | null {
51  if (!v) {
52    return null;
53  }
54  return value(v, params);
55}
56
57// A basic type for the JSON-like value, comprising a primitive type (string)
58// and composite types (arrays and dicts).
59export type Value = StringValue | Array | Dict;
60
61// Dictionary type.
62export type Dict = {
63  kind: 'DICT';
64  items: {[name: string]: Value};
65} & ValueParams;
66
67// Helper function to simplify creation of a dictionary.
68// This function accepts and filters out nulls as values in the passed
69// dictionary (useful for simplifying the code to render optional values).
70export function dict(
71  items: {[name: string]: Value | null},
72  params?: ValueParams,
73): Dict {
74  const result: {[name: string]: Value} = {};
75  for (const [name, value] of Object.entries(items)) {
76    if (value !== null) {
77      result[name] = value;
78    }
79  }
80  return {
81    kind: 'DICT',
82    items: result,
83    ...params,
84  };
85}
86
87// Array type.
88export type Array = {
89  kind: 'ARRAY';
90  items: Value[];
91} & ValueParams;
92
93// Helper function to simplify creation of an array.
94// This function accepts and filters out nulls in the passed array (useful for
95// simplifying the code to render optional values).
96export function array(items: (Value | null)[], params?: ValueParams): Array {
97  return {
98    kind: 'ARRAY',
99    items: items.filter((item: Value | null) => item !== null) as Value[],
100    ...params,
101  };
102}
103
104// Parameters for displaying a button next to a value to perform
105// the context-dependent action (i.e. go to the corresponding slice).
106type ButtonParams = {
107  action: () => void;
108  hoverText?: string;
109  icon?: string;
110};
111
112// Customisation parameters which apply to any Value (e.g. context menu).
113interface ValueParams {
114  contextMenu?: PopupMenuItem[];
115}
116
117// Customisation parameters which apply for a primitive value (e.g. showing
118// button next to a string, or making it clickable, or adding onhover effect).
119interface StringValueParams extends ValueParams {
120  leftButton?: ButtonParams;
121  rightButton?: ButtonParams;
122}
123
124export function isArray(value: Value): value is Array {
125  return value.kind === 'ARRAY';
126}
127
128export function isDict(value: Value): value is Dict {
129  return value.kind === 'DICT';
130}
131
132export function isStringValue(value: Value): value is StringValue {
133  return !isArray(value) && !isDict(value);
134}
135
136// Recursively render the given value and its children, returning a list of
137// vnodes corresponding to the nodes of the table.
138function renderValue(name: string, value: Value): m.Children {
139  const left = [
140    name,
141    value.contextMenu
142      ? m(PopupMenuButton, {
143          icon: 'arrow_drop_down',
144          items: value.contextMenu,
145        })
146      : null,
147  ];
148  if (isArray(value)) {
149    const nodes = value.items.map((value: Value, index: number) => {
150      return renderValue(`[${index}]`, value);
151    });
152    return m(TreeNode, {left, right: `array[${nodes.length}]`}, nodes);
153  } else if (isDict(value)) {
154    const nodes: m.Children[] = [];
155    for (const key of Object.keys(value.items)) {
156      nodes.push(renderValue(key, value.items[key]));
157    }
158    return m(TreeNode, {left, right: `dict`}, nodes);
159  } else {
160    const renderButton = (button?: ButtonParams) => {
161      if (!button) {
162        return null;
163      }
164      return m(
165        'i.material-icons.grey',
166        {
167          onclick: button.action,
168          title: button.hoverText,
169        },
170        button.icon ? button.icon : 'call_made',
171      );
172    };
173    if (value.kind === 'STRING') {
174      const right = [
175        renderButton(value.leftButton),
176        m('span', value.value),
177        renderButton(value.rightButton),
178      ];
179      return m(TreeNode, {left, right});
180    } else {
181      return null;
182    }
183  }
184}
185
186// Render a given dictionary to a tree.
187export function renderDict(dict: Dict): m.Child {
188  const rows: m.Children[] = [];
189  for (const key of Object.keys(dict.items)) {
190    rows.push(renderValue(key, dict.items[key]));
191  }
192  return m(Tree, rows);
193}
194