• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mac-s-g / react-json-view / #3170

30 Jun 2022 04:19PM UTC coverage: 78.095% (-5.1%) from 83.209%
#3170

push

web-flow
Merge pull request #3 from grapherjs/feat/highlighter

feat: add highlighter effect on object keys as well

290 of 379 branches covered (76.52%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 2 files covered. (100.0%)

21 existing lines in 1 file now uncovered.

530 of 671 relevant lines covered (78.99%)

41.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.59
/src/js/components/VariableEditor.js
1
import React from 'react';
2
import AutosizeTextarea from 'react-textarea-autosize';
3
import { highlight } from './../helpers/highlight-words-core';
4
import { toType } from './../helpers/util';
5
import dispatcher from './../helpers/dispatcher';
6
import parseInput from './../helpers/parseInput';
7
import stringifyVariable from './../helpers/stringifyVariable';
8
import CopyToClipboard from './CopyToClipboard';
9

10
//data type components
11
import {
12
    JsonBoolean,
13
    JsonDate,
14
    JsonFloat,
15
    JsonFunction,
16
    JsonInteger,
17
    JsonNan,
18
    JsonNull,
19
    JsonRegexp,
20
    JsonString,
21
    JsonUndefined
22
} from './DataTypes/DataTypes';
23

24
//clibboard icon
25
import { Edit, CheckCircle, RemoveCircle as Remove } from './icons';
26

27
//theme
28
import Theme from './../themes/getStyle';
29

30
class VariableEditor extends React.PureComponent {
31
    constructor(props) {
32
        super(props);
85✔
33
        this.state = {
85✔
34
            editMode: false,
35
            editValue: '',
36
            hovered: false,
37
            renameKey: false,
38
            parsedInput: {
39
                type: false,
40
                value: null
41
            }
42
        };
43
    }
44

45
    render() {
46
        const {
47
            variable,
48
            singleIndent,
49
            type,
50
            theme,
51
            namespace,
52
            indentWidth,
53
            enableClipboard,
54
            onEdit,
55
            onDelete,
56
            onSelect,
57
            displayArrayKey,
58
            quotesOnKeys,
59
            //
60
            autoEscape,
61
            caseSensitive,
62
            searchWords,
63
            highlightStyle,
64
            highlightClassName
65
        } = this.props;
120✔
66
        const { editMode } = this.state;
120✔
67

68
        const options = {
120✔
69
            autoEscape,
70
            caseSensitive,
71
            searchWords,
72
            highlightStyle,
73
            highlightClassName
74
        };
75

76
        return (
120✔
77
            <div
78
                {...Theme(theme, 'objectKeyVal', {
79
                    paddingLeft: indentWidth * singleIndent
80
                })}
81
                onMouseEnter={() =>
82
                    this.setState({ ...this.state, hovered: true })
×
83
                }
84
                onMouseLeave={() =>
85
                    this.setState({ ...this.state, hovered: false })
×
86
                }
87
                class="variable-row"
88
                key={variable.name}
89
            >
90
                {type == 'array' ? (
120✔
91
                    displayArrayKey ? (
26✔
92
                        <span
93
                            {...Theme(theme, 'array-key')}
94
                            key={variable.name + '_' + namespace}
95
                        >
96
                            {highlight({
97
                                ...options,
98
                                textToHighlight: variable.name
99
                            })}
100
                            <div {...Theme(theme, 'colon')}>:</div>
101
                        </span>
102
                    ) : null
103
                ) : (
104
                    <span>
105
                        <span
106
                            {...Theme(theme, 'object-name')}
107
                            class="object-key"
108
                            key={variable.name + '_' + namespace}
109
                        >
110
                            {!!quotesOnKeys && (
145✔
111
                                <span style={{ verticalAlign: 'top' }}>"</span>
112
                            )}
113
                            <span style={{ display: 'inline-block' }}>
114
                                {highlight({
115
                                    ...options,
116
                                    textToHighlight: variable.name
117
                                })}
118
                            </span>
119
                            {!!quotesOnKeys && (
145✔
120
                                <span style={{ verticalAlign: 'top' }}>"</span>
121
                            )}
122
                        </span>
123
                        <span {...Theme(theme, 'colon')}>:</span>
124
                    </span>
125
                )}
126
                <div
127
                    class="variable-value"
128
                    onClick={
129
                        onSelect === false && onEdit === false
299✔
130
                            ? null
131
                            : e => {
UNCOV
132
                                  let location = [...namespace];
×
133
                                  if (
×
134
                                      (e.ctrlKey || e.metaKey) &&
×
135
                                      onEdit !== false
136
                                  ) {
UNCOV
137
                                      this.prepopInput(variable);
×
UNCOV
138
                                  } else if (onSelect !== false) {
×
UNCOV
139
                                      location.shift();
×
UNCOV
140
                                      onSelect({
×
141
                                          ...variable,
142
                                          namespace: location
143
                                      });
144
                                  }
145
                              }
146
                    }
147
                    {...Theme(theme, 'variableValue', {
148
                        cursor: onSelect === false ? 'default' : 'pointer'
120✔
149
                    })}
150
                >
151
                    {this.getValue(variable, editMode)}
152
                </div>
153
                {enableClipboard ? (
120✔
154
                    <CopyToClipboard
155
                        rowHovered={this.state.hovered}
156
                        hidden={editMode}
157
                        src={variable.value}
158
                        clickCallback={enableClipboard}
159
                        {...{ theme, namespace: [...namespace, variable.name] }}
160
                    />
161
                ) : null}
162
                {onEdit !== false && editMode == false
300✔
163
                    ? this.getEditIcon()
164
                    : null}
165
                {onDelete !== false && editMode == false
301✔
166
                    ? this.getRemoveIcon()
167
                    : null}
168
            </div>
169
        );
170
    }
171

172
    getEditIcon = () => {
85✔
173
        const { variable, theme } = this.props;
44✔
174

175
        return (
44✔
176
            <div
177
                class="click-to-edit"
178
                style={{
179
                    verticalAlign: 'top',
180
                    display: this.state.hovered ? 'inline-block' : 'none'
44!
181
                }}
182
            >
183
                <Edit
184
                    class="click-to-edit-icon"
185
                    {...Theme(theme, 'editVarIcon')}
186
                    onClick={() => {
187
                        this.prepopInput(variable);
13✔
188
                    }}
189
                />
190
            </div>
191
        );
192
    };
193

194
    prepopInput = variable => {
85✔
195
        if (this.props.onEdit !== false) {
13!
196
            const stringifiedValue = stringifyVariable(variable.value);
13✔
197
            const detected = parseInput(stringifiedValue);
13✔
198
            this.setState({
13✔
199
                editMode: true,
200
                editValue: stringifiedValue,
201
                parsedInput: {
202
                    type: detected.type,
203
                    value: detected.value
204
                }
205
            });
206
        }
207
    };
208

209
    getRemoveIcon = () => {
85✔
210
        const { variable, namespace, theme, rjvId } = this.props;
45✔
211

212
        return (
45✔
213
            <div
214
                class="click-to-remove"
215
                style={{
216
                    verticalAlign: 'top',
217
                    display: this.state.hovered ? 'inline-block' : 'none'
45!
218
                }}
219
            >
220
                <Remove
221
                    class="click-to-remove-icon"
222
                    {...Theme(theme, 'removeVarIcon')}
223
                    onClick={() => {
UNCOV
224
                        dispatcher.dispatch({
×
225
                            name: 'VARIABLE_REMOVED',
226
                            rjvId: rjvId,
227
                            data: {
228
                                name: variable.name,
229
                                namespace: namespace,
230
                                existing_value: variable.value,
231
                                variable_removed: true
232
                            }
233
                        });
234
                    }}
235
                />
236
            </div>
237
        );
238
    };
239

240
    getValue = (variable, editMode) => {
85✔
241
        const type = editMode ? false : variable.type;
120✔
242
        const { props } = this;
120✔
243
        switch (type) {
120!
244
            case false:
245
                return this.getEditInput();
16✔
246
            case 'string':
247
                return <JsonString value={variable.value} {...props} />;
41✔
248
            case 'integer':
249
                return <JsonInteger value={variable.value} {...props} />;
24✔
250
            case 'float':
251
                return <JsonFloat value={variable.value} {...props} />;
2✔
252
            case 'boolean':
253
                return <JsonBoolean value={variable.value} {...props} />;
9✔
254
            case 'function':
255
                return <JsonFunction value={variable.value} {...props} />;
5✔
256
            case 'null':
257
                return <JsonNull {...props} />;
5✔
258
            case 'nan':
259
                return <JsonNan {...props} />;
5✔
260
            case 'undefined':
261
                return <JsonUndefined {...props} />;
2✔
262
            case 'date':
UNCOV
263
                return <JsonDate value={variable.value} {...props} />;
×
264
            case 'regexp':
265
                return <JsonRegexp value={variable.value} {...props} />;
4✔
266
            default:
267
                // catch-all for types that weren't anticipated
268
                return (
7✔
269
                    <div class="object-value">
270
                        {JSON.stringify(variable.value)}
271
                    </div>
272
                );
273
        }
274
    };
275

276
    getEditInput = () => {
85✔
277
        const { theme } = this.props;
16✔
278
        const { editValue } = this.state;
16✔
279

280
        return (
16✔
281
            <div>
282
                <AutosizeTextarea
283
                    type="text"
284
                    inputRef={input => input && input.focus()}
×
285
                    value={editValue}
286
                    class="variable-editor"
287
                    onChange={event => {
UNCOV
288
                        const value = event.target.value;
×
UNCOV
289
                        const detected = parseInput(value);
×
UNCOV
290
                        this.setState({
×
291
                            editValue: value,
292
                            parsedInput: {
293
                                type: detected.type,
294
                                value: detected.value
295
                            }
296
                        });
297
                    }}
298
                    onKeyDown={e => {
UNCOV
299
                        switch (e.key) {
×
300
                            case 'Escape': {
301
                                this.setState({
×
302
                                    editMode: false,
303
                                    editValue: ''
304
                                });
305
                                break;
×
306
                            }
307
                            case 'Enter': {
UNCOV
308
                                if (e.ctrlKey || e.metaKey) {
×
UNCOV
309
                                    this.submitEdit(true);
×
310
                                }
UNCOV
311
                                break;
×
312
                            }
313
                        }
UNCOV
314
                        e.stopPropagation();
×
315
                    }}
316
                    placeholder="update this value"
317
                    minRows={2}
318
                    {...Theme(theme, 'edit-input')}
319
                />
320
                <div {...Theme(theme, 'edit-icon-container')}>
321
                    <Remove
322
                        class="edit-cancel"
323
                        {...Theme(theme, 'cancel-icon')}
324
                        onClick={() => {
325
                            this.setState({ editMode: false, editValue: '' });
2✔
326
                        }}
327
                    />
328
                    <CheckCircle
329
                        class="edit-check string-value"
330
                        {...Theme(theme, 'check-icon')}
331
                        onClick={() => {
332
                            this.submitEdit();
1✔
333
                        }}
334
                    />
335
                    <div>{this.showDetected()}</div>
336
                </div>
337
            </div>
338
        );
339
    };
340

341
    submitEdit = submit_detected => {
85✔
342
        const { variable, namespace, rjvId } = this.props;
1✔
343
        const { editValue, parsedInput } = this.state;
1✔
344
        let new_value = editValue;
1✔
345
        if (submit_detected && parsedInput.type) {
1!
UNCOV
346
            new_value = parsedInput.value;
×
347
        }
348
        this.setState({
1✔
349
            editMode: false
350
        });
351
        dispatcher.dispatch({
1✔
352
            name: 'VARIABLE_UPDATED',
353
            rjvId: rjvId,
354
            data: {
355
                name: variable.name,
356
                namespace: namespace,
357
                existing_value: variable.value,
358
                new_value: new_value,
359
                variable_removed: false
360
            }
361
        });
362
    };
363

364
    showDetected = () => {
85✔
365
        const { theme, variable, namespace, rjvId } = this.props;
16✔
366
        const { type, value } = this.state.parsedInput;
16✔
367
        const detected = this.getDetectedInput();
16✔
368
        if (detected) {
16✔
369
            return (
8✔
370
                <div>
371
                    <div {...Theme(theme, 'detected-row')}>
372
                        {detected}
373
                        <CheckCircle
374
                            class="edit-check detected"
375
                            style={{
376
                                verticalAlign: 'top',
377
                                paddingLeft: '3px',
378
                                ...Theme(theme, 'check-icon').style
379
                            }}
380
                            onClick={() => {
UNCOV
381
                                this.submitEdit(true);
×
382
                            }}
383
                        />
384
                    </div>
385
                </div>
386
            );
387
        }
388
    };
389

390
    getDetectedInput = () => {
85✔
391
        const { parsedInput } = this.state;
16✔
392
        const { type, value } = parsedInput;
16✔
393
        const { props } = this;
16✔
394
        const { theme } = props;
16✔
395

396
        if (type !== false) {
16✔
397
            switch (type.toLowerCase()) {
8!
398
                case 'object':
399
                    return (
1✔
400
                        <span>
401
                            <span
402
                                style={{
403
                                    ...Theme(theme, 'brace').style,
404
                                    cursor: 'default'
405
                                }}
406
                            >
407
                                {'{'}
408
                            </span>
409
                            <span
410
                                style={{
411
                                    ...Theme(theme, 'ellipsis').style,
412
                                    cursor: 'default'
413
                                }}
414
                            >
415
                                ...
416
                            </span>
417
                            <span
418
                                style={{
419
                                    ...Theme(theme, 'brace').style,
420
                                    cursor: 'default'
421
                                }}
422
                            >
423
                                {'}'}
424
                            </span>
425
                        </span>
426
                    );
427
                case 'array':
428
                    return (
1✔
429
                        <span>
430
                            <span
431
                                style={{
432
                                    ...Theme(theme, 'brace').style,
433
                                    cursor: 'default'
434
                                }}
435
                            >
436
                                {'['}
437
                            </span>
438
                            <span
439
                                style={{
440
                                    ...Theme(theme, 'ellipsis').style,
441
                                    cursor: 'default'
442
                                }}
443
                            >
444
                                ...
445
                            </span>
446
                            <span
447
                                style={{
448
                                    ...Theme(theme, 'brace').style,
449
                                    cursor: 'default'
450
                                }}
451
                            >
452
                                {']'}
453
                            </span>
454
                        </span>
455
                    );
456
                case 'string':
UNCOV
457
                    return <JsonString value={value} {...props} />;
×
458
                case 'integer':
459
                    return <JsonInteger value={value} {...props} />;
2✔
460
                case 'float':
461
                    return <JsonFloat value={value} {...props} />;
1✔
462
                case 'boolean':
UNCOV
463
                    return <JsonBoolean value={value} {...props} />;
×
464
                case 'function':
UNCOV
465
                    return <JsonFunction value={value} {...props} />;
×
466
                case 'null':
467
                    return <JsonNull {...props} />;
1✔
468
                case 'nan':
469
                    return <JsonNan {...props} />;
1✔
470
                case 'undefined':
471
                    return <JsonUndefined {...props} />;
1✔
472
                case 'date':
UNCOV
473
                    return <JsonDate value={new Date(value)} {...props} />;
×
474
            }
475
        }
476
    };
477
}
478

479
//export component
480
export default VariableEditor;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc