Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/web/ui/src/features/river-js/RiverValue.tsx
5371 views
1
import { FC, Fragment, ReactElement } from 'react';
2
3
import { ObjectField, Value, ValueType } from './types';
4
5
import styles from './RiverValue.module.css';
6
7
export interface RiverValueProps {
8
value: Value;
9
}
10
11
/**
12
* RiverValue emits a paragraph which represents a River value.
13
*/
14
export const RiverValue: FC<RiverValueProps> = (props) => {
15
return (
16
<p className={styles.value}>
17
<ValueRenderer value={props.value} indentLevel={0} />
18
</p>
19
);
20
};
21
22
type valueRendererProps = RiverValueProps & {
23
indentLevel: number;
24
};
25
26
const ValueRenderer: FC<valueRendererProps> = (props) => {
27
const value = props.value;
28
29
switch (value.type) {
30
case ValueType.NULL:
31
return <span className={styles.literal}>null</span>;
32
33
case ValueType.NUMBER:
34
return <span className={styles.literal}>{value.value.toString()}</span>;
35
36
case ValueType.STRING:
37
return <span className={styles.string}>"{escapeString(value.value)}"</span>;
38
39
case ValueType.BOOL:
40
if (value.value) {
41
return <span className={styles.literal}>true</span>;
42
}
43
return <span className={styles.literal}>false</span>;
44
45
case ValueType.ARRAY:
46
return (
47
<>
48
<span>[</span>
49
{value.value.map((element, idx) => {
50
return (
51
<Fragment key={idx.toString()}>
52
<ValueRenderer value={element} indentLevel={props.indentLevel} />
53
{idx + 1 < value.value.length ? <span>, </span> : null}
54
</Fragment>
55
);
56
})}
57
<span>]</span>
58
</>
59
);
60
61
case ValueType.OBJECT:
62
if (value.value.length === 0) {
63
// No elements; return `{}` without any line breaks.
64
return (
65
<>
66
<span>&#123;</span>
67
<span>&#125;</span>
68
</>
69
);
70
}
71
72
const partitions = partitionFields(value.value);
73
74
return (
75
<>
76
<span>&#123;</span>
77
<br />
78
{partitions.map((partition) => {
79
// Find the maximum field length across all fields in this
80
// partition.
81
const keyLength = partitionKeyLength(partition);
82
83
return partition.map((element, index) => {
84
return (
85
<Fragment key={index.toString()}>
86
{getLinePrefix(props.indentLevel + 1)}
87
<span>{partitionKey(element, keyLength)} = </span>
88
<ValueRenderer value={element.value} indentLevel={props.indentLevel + 1} />
89
<span>,</span>
90
<br />
91
</Fragment>
92
);
93
});
94
})}
95
{getLinePrefix(props.indentLevel)}
96
<span>&#125;</span>
97
</>
98
);
99
100
case ValueType.FUNCTION:
101
return <span className={styles.special}>{value.value}</span>;
102
103
case ValueType.CAPSULE:
104
return <span className={styles.special}>{value.value}</span>;
105
}
106
};
107
108
/**
109
* partitionFields partitions fields in an object by fields which should have
110
* their equal signs aligned.
111
*
112
* A field which crosses multiple lines (i.e., recursively contains an object
113
* with more than one element) will cause a partition break, placing subsequent
114
* fields in another partition.
115
*/
116
function partitionFields(fields: ObjectField[]): ObjectField[][] {
117
const partitions = [];
118
119
let currentPartition: ObjectField[] = [];
120
fields.forEach((field) => {
121
currentPartition.push(field);
122
123
if (multilinedValue(field.value)) {
124
// Fields which cross multiple lines cause a partition break.
125
partitions.push(currentPartition);
126
currentPartition = [];
127
}
128
});
129
130
if (currentPartition.length !== 0) {
131
partitions.push(currentPartition);
132
}
133
134
return partitions;
135
}
136
137
/** multilinedValue returns true if value recursively crosses multiple lines. */
138
function multilinedValue(value: Value): boolean {
139
switch (value.type) {
140
case ValueType.OBJECT:
141
// River objects cross more than one line whenever there is at least one
142
// element.
143
return value.value.length > 0;
144
145
case ValueType.ARRAY:
146
// River arrays cross more than one line if any of their elements cross
147
// more than one line.
148
return value.value.some((v) => multilinedValue(v));
149
}
150
151
// Other values never cross line barriers.
152
return false;
153
}
154
155
/**
156
* partitionKeyLength returns the length of keys within the partition. The
157
* length is determined by the longest field name in the partition.
158
*/
159
function partitionKeyLength(partition: ObjectField[]): number {
160
let keyLength = 0;
161
162
partition.forEach((f) => {
163
const fieldLength = partitionKey(f, 0).length;
164
if (fieldLength > keyLength) {
165
keyLength = fieldLength;
166
}
167
});
168
169
return keyLength;
170
}
171
172
/**
173
* partitionKey returns the text to use to display a key for a field within a
174
* partition.
175
*/
176
function partitionKey(field: ObjectField, keyLength: number): string {
177
let key = field.key;
178
if (!validIdentifier(key)) {
179
// Keys which aren't valid identifiers should be wrapped in quotes.
180
key = `"${key}"`;
181
}
182
183
if (key.length < keyLength) {
184
return key + ' '.repeat(keyLength - key.length);
185
}
186
return key;
187
}
188
189
function getLinePrefix(indentLevel: number): ReactElement | null {
190
if (indentLevel === 0) {
191
return null;
192
}
193
return <span>{'\t'.repeat(indentLevel)}</span>;
194
}
195
196
/**
197
* validIdentifier reports whether the input is a valid River identifier.
198
*/
199
function validIdentifier(input: string): boolean {
200
return /^[_a-z][_a-z0-9]*$/i.test(input);
201
}
202
203
/**
204
* escapeString escapes special characters in a string so they can be printed
205
* inside a River string literal.
206
*/
207
function escapeString(input: string): string {
208
// TODO(rfratto): this should also escape Unicode characters into \u and \U
209
// forms.
210
return input.replace(/[\b\f\n\r\t\v\0'"\\]/g, (match) => {
211
switch (match) {
212
case '\b':
213
return '\\b';
214
case '\f':
215
return '\\f';
216
case '\n':
217
return '\\n';
218
case '\r':
219
return '\\r';
220
case '\t':
221
return '\\t';
222
case '\v':
223
return '\\v';
224
case "'":
225
return "\\'";
226
case '"':
227
return '\\"';
228
case '\\':
229
return '\\\\';
230
}
231
return '';
232
});
233
}
234
235