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