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

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

01 Dec 2022 10:27AM UTC coverage: 80.707% (-2.5%) from 83.246%
#1382

push

asburyj
updated package.json peer dependencies for react and react-dom

281 of 366 branches covered (76.78%)

Branch coverage included in aggregate %.

518 of 624 relevant lines covered (83.01%)

35.15 hits per line

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

66.3
/src/js/components/VariableEditor.js
1
import React from 'react';
2
import AutosizeTextarea from 'react-textarea-autosize';
3

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);
58✔
33
        this.state = {
58✔
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
            highlight
60
        } = this.props;
93✔
61
        const { editMode } = this.state;
93✔
62

63
        let styles = {};
93✔
64
        let keyStyles = {};
93✔
65
        let valueStyles = {};
93✔
66

67
        if (highlight && highlight.length > 0 && namespace) {
93!
68
            const highlightObject = highlight.find((h) => h.key === variable.name);
×
69
            if (highlightObject) {
×
70
                styles = highlightObject.styles;
×
71
                keyStyles = highlightObject.keyStyles;
×
72
                valueStyles = highlightObject.valueStyles;
×
73
            }
74
        }
75

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

165
    getEditIcon = () => {
58✔
166
        const { variable, theme } = this.props;
17✔
167

168
        return (
17✔
169
            <div
170
                class="click-to-edit"
171
                style={{
172
                    verticalAlign: 'top',
173
                    display: this.state.hovered ? 'inline-block' : 'none'
17!
174
                }}
175
            >
176
                <Edit
177
                    class="click-to-edit-icon"
178
                    {...Theme(theme, 'editVarIcon')}
179
                    onClick={() => {
180
                        this.prepopInput(variable);
13✔
181
                    }}
182
                />
183
            </div>
184
        );
185
    };
186

187
    prepopInput = variable => {
58✔
188
        if (this.props.onEdit !== false) {
13!
189
            const stringifiedValue = stringifyVariable(variable.value);
13✔
190
            const detected = parseInput(stringifiedValue);
13✔
191
            this.setState({
13✔
192
                editMode: true,
193
                editValue: stringifiedValue,
194
                parsedInput: {
195
                    type: detected.type,
196
                    value: detected.value
197
                }
198
            });
199
        }
200
    };
201

202
    getRemoveIcon = () => {
58✔
203
        const { variable, namespace, theme, rjvId } = this.props;
18✔
204

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

233
    getValue = (variable, editMode, valueStyles) => {
58✔
234
        const type = editMode ? false : variable.type;
93✔
235
        const { props } = this;
93✔
236
        switch (type) {
93!
237
            case false:
238
                return this.getEditInput();
16✔
239
            case 'string':
240
                return <JsonString value={variable.value} {...props} valueStyles={valueStyles} />;
24✔
241
            case 'integer':
242
                return <JsonInteger value={variable.value} {...props} />;
21✔
243
            case 'float':
244
                return <JsonFloat value={variable.value} {...props} />;
1✔
245
            case 'boolean':
246
                return <JsonBoolean value={variable.value} {...props} />;
7✔
247
            case 'function':
248
                return <JsonFunction value={variable.value} {...props} />;
4✔
249
            case 'null':
250
                return <JsonNull {...props} />;
4✔
251
            case 'nan':
252
                return <JsonNan {...props} />;
4✔
253
            case 'undefined':
254
                return <JsonUndefined {...props} />;
1✔
255
            case 'date':
256
                return <JsonDate value={variable.value} {...props} />;
×
257
            case 'regexp':
258
                return <JsonRegexp value={variable.value} {...props} />;
4✔
259
            default:
260
                // catch-all for types that weren't anticipated
261
                return (
7✔
262
                    <div class="object-value">
263
                        {JSON.stringify(variable.value)}
264
                    </div>
265
                );
266
        }
267
    };
268

269
    getEditInput = () => {
58✔
270
        const { theme } = this.props;
16✔
271
        const { editValue } = this.state;
16✔
272

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

334
    submitEdit = submit_detected => {
58✔
335
        const { variable, namespace, rjvId } = this.props;
1✔
336
        const { editValue, parsedInput } = this.state;
1✔
337
        let new_value = editValue;
1✔
338
        if (submit_detected && parsedInput.type) {
1!
339
            new_value = parsedInput.value;
×
340
        }
341
        this.setState({
1✔
342
            editMode: false
343
        });
344
        dispatcher.dispatch({
1✔
345
            name: 'VARIABLE_UPDATED',
346
            rjvId: rjvId,
347
            data: {
348
                name: variable.name,
349
                namespace: namespace,
350
                existing_value: variable.value,
351
                new_value: new_value,
352
                variable_removed: false
353
            }
354
        });
355
    };
356

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

383
    getDetectedInput = () => {
58✔
384
        const { parsedInput } = this.state;
16✔
385
        const { type, value } = parsedInput;
16✔
386
        const { props } = this;
16✔
387
        const { theme } = props;
16✔
388

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

472
//export component
473
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