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