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

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

05 Feb 2021 11:19PM UTC coverage: 80.144% (-3.1%) from 83.209%
#3109

push

web-flow
Merge pull request #328 from mac-s-g/bump-minor

quotesOnKeys bugfix

317 of 417 branches covered (76.02%)

Branch coverage included in aggregate %.

575 of 696 relevant lines covered (82.61%)

42.9 hits per line

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

71.02
/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);
87✔
33
        this.state = {
87✔
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
        } = this.props;
122✔
60
        const { editMode } = this.state;
122✔
61
        return (
122✔
62
            <div
63
                {...Theme(theme, 'objectKeyVal', {
64
                    paddingLeft: indentWidth * singleIndent
65
                })}
66
                onMouseEnter={() =>
67
                    this.setState({ ...this.state, hovered: true })
×
68
                }
69
                onMouseLeave={() =>
70
                    this.setState({ ...this.state, hovered: false })
×
71
                }
72
                class="variable-row"
73
                key={variable.name}
74
            >
75
                {type == 'array' ? (
122✔
76
                    displayArrayKey ? (
122✔
77
                        <span
122✔
78
                            {...Theme(theme, 'array-key')}
122!
79
                            key={variable.name + '_' + namespace}
122!
80
                        >
81
                            {variable.name}
82
                            <div {...Theme(theme, 'colon')}>:</div>
×
83
                        </span>
×
84
                    ) : null
85
                ) : (
86
                    <span>
122✔
87
                        <span
88
                            {...Theme(theme, 'object-name')}
89
                            class="object-key"
90
                            key={variable.name + '_' + namespace}
122✔
91
                        >
26✔
92
                            {!!quotesOnKeys && (
93
                                <span style={{ verticalAlign: 'top' }}>"</span>
94
                            )}
95
                            <span style={{ display: 'inline-block' }}>
96
                                {variable.name}
97
                            </span>
98
                            {!!quotesOnKeys && (
99
                                <span style={{ verticalAlign: 'top' }}>"</span>
100
                            )}
101
                        </span>
102
                        <span {...Theme(theme, 'colon')}>:</span>
103
                    </span>
104
                )}
105
                <div
106
                    class="variable-value"
107
                    onClick={
149✔
108
                        onSelect === false && onEdit === false
109
                            ? null
110
                            : e => {
111
                                  let location = [...namespace];
112
                                  if (
113
                                      (e.ctrlKey || e.metaKey) &&
149✔
114
                                      onEdit !== false
115
                                  ) {
116
                                      this.prepopInput(variable);
117
                                  } else if (onSelect !== false) {
118
                                      location.shift();
119
                                      onSelect({
120
                                          ...variable,
121
                                          namespace: location
122
                                      });
123
                                  }
305✔
124
                              }
125
                    }
126
                    {...Theme(theme, 'variableValue', {
×
127
                        cursor: onSelect === false ? 'default' : 'pointer'
×
128
                    })}
×
129
                >
130
                    {this.getValue(variable, editMode)}
131
                </div>
×
132
                {enableClipboard ? (
×
133
                    <CopyToClipboard
×
134
                        rowHovered={this.state.hovered}
×
135
                        hidden={editMode}
136
                        src={variable.value}
137
                        clickCallback={enableClipboard}
138
                        {...{ theme, namespace: [...namespace, variable.name] }}
139
                    />
140
                ) : null}
141
                {onEdit !== false && editMode == false
142
                    ? this.getEditIcon()
122✔
143
                    : null}
144
                {onDelete !== false && editMode == false
145
                    ? this.getRemoveIcon()
146
                    : null}
147
            </div>
122✔
148
        );
149
    }
150

151
    getEditIcon = () => {
152
        const { variable, theme } = this.props;
153

154
        return (
155
            <div
156
                class="click-to-edit"
304✔
157
                style={{
158
                    verticalAlign: 'top',
159
                    display: this.state.hovered ? 'inline-block' : 'none'
305✔
160
                }}
161
            >
162
                <Edit
163
                    class="click-to-edit-icon"
164
                    {...Theme(theme, 'editVarIcon')}
165
                    onClick={() => {
166
                        this.prepopInput(variable);
87✔
167
                    }}
44✔
168
                />
169
            </div>
44✔
170
        );
171
    };
172

173
    prepopInput = variable => {
174
        if (this.props.onEdit !== false) {
44!
175
            const stringifiedValue = stringifyVariable(variable.value);
176
            const detected = parseInput(stringifiedValue);
177
            this.setState({
178
                editMode: true,
179
                editValue: stringifiedValue,
180
                parsedInput: {
181
                    type: detected.type,
13✔
182
                    value: detected.value
183
                }
184
            });
185
        }
186
    };
187

188
    getRemoveIcon = () => {
87✔
189
        const { variable, namespace, theme, rjvId } = this.props;
13!
190

13✔
191
        return (
13✔
192
            <div
13✔
193
                class="click-to-remove"
194
                style={{
195
                    verticalAlign: 'top',
196
                    display: this.state.hovered ? 'inline-block' : 'none'
197
                }}
198
            >
199
                <Remove
200
                    class="click-to-remove-icon"
201
                    {...Theme(theme, 'removeVarIcon')}
202
                    onClick={() => {
203
                        dispatcher.dispatch({
87✔
204
                            name: 'VARIABLE_REMOVED',
45✔
205
                            rjvId: rjvId,
206
                            data: {
45✔
207
                                name: variable.name,
208
                                namespace: namespace,
209
                                existing_value: variable.value,
210
                                variable_removed: true
211
                            }
45!
212
                        });
213
                    }}
214
                />
215
            </div>
216
        );
217
    };
218

×
219
    getValue = (variable, editMode) => {
220
        const type = editMode ? false : variable.type;
221
        const { props } = this;
222
        switch (type) {
223
            case false:
224
                return this.getEditInput();
225
            case 'string':
226
                return <JsonString value={variable.value} {...props} />;
227
            case 'integer':
228
                return <JsonInteger value={variable.value} {...props} />;
229
            case 'float':
230
                return <JsonFloat value={variable.value} {...props} />;
231
            case 'boolean':
232
                return <JsonBoolean value={variable.value} {...props} />;
233
            case 'function':
234
                return <JsonFunction value={variable.value} {...props} />;
87✔
235
            case 'null':
122✔
236
                return <JsonNull {...props} />;
122✔
237
            case 'nan':
122!
238
                return <JsonNan {...props} />;
239
            case 'undefined':
16✔
240
                return <JsonUndefined {...props} />;
241
            case 'date':
39✔
242
                return <JsonDate value={variable.value} {...props} />;
243
            case 'regexp':
28✔
244
                return <JsonRegexp value={variable.value} {...props} />;
245
            default:
2✔
246
                // catch-all for types that weren't anticipated
247
                return (
9✔
248
                    <div class="object-value">
249
                        {JSON.stringify(variable.value)}
5✔
250
                    </div>
251
                );
5✔
252
        }
253
    };
5✔
254

255
    getEditInput = () => {
2✔
256
        const { theme } = this.props;
257
        const { editValue } = this.state;
×
258

259
        return (
4✔
260
            <div>
261
                <AutosizeTextarea
262
                    type="text"
7✔
263
                    inputRef={input => input && input.focus()}
264
                    value={editValue}
265
                    class="variable-editor"
266
                    onChange={event => {
267
                        const value = event.target.value;
268
                        const detected = parseInput(value);
269
                        this.setState({
270
                            editValue: value,
87✔
271
                            parsedInput: {
16✔
272
                                type: detected.type,
16✔
273
                                value: detected.value
274
                            }
16✔
275
                        });
276
                    }}
277
                    onKeyDown={e => {
278
                        switch (e.key) {
×
279
                            case 'Escape': {
280
                                this.setState({
281
                                    editMode: false,
282
                                    editValue: ''
×
283
                                });
×
284
                                break;
×
285
                            }
286
                            case 'Enter': {
287
                                if (e.ctrlKey || e.metaKey) {
288
                                    this.submitEdit(true);
289
                                }
290
                                break;
291
                            }
292
                        }
293
                        e.stopPropagation();
×
294
                    }}
295
                    placeholder="update this value"
×
296
                    {...Theme(theme, 'edit-input')}
297
                />
298
                <div {...Theme(theme, 'edit-icon-container')}>
299
                    <Remove
×
300
                        class="edit-cancel"
301
                        {...Theme(theme, 'cancel-icon')}
302
                        onClick={() => {
303
                            this.setState({ editMode: false, editValue: '' });
×
304
                        }}
×
305
                    />
306
                    <CheckCircle
307
                        class="edit-check string-value"
×
308
                        {...Theme(theme, 'check-icon')}
309
                        onClick={() => {
310
                            this.submitEdit();
311
                        }}
312
                    />
313
                    <div>{this.showDetected()}</div>
314
                </div>
315
            </div>
316
        );
317
    };
2✔
318

319
    submitEdit = submit_detected => {
320
        const { variable, namespace, rjvId } = this.props;
321
        const { editValue, parsedInput } = this.state;
322
        let new_value = editValue;
323
        if (submit_detected && parsedInput.type) {
324
            new_value = parsedInput.value;
1✔
325
        }
326
        this.setState({
327
            editMode: false
328
        });
329
        dispatcher.dispatch({
330
            name: 'VARIABLE_UPDATED',
331
            rjvId: rjvId,
332
            data: {
333
                name: variable.name,
87✔
334
                namespace: namespace,
1✔
335
                existing_value: variable.value,
1✔
336
                new_value: new_value,
1✔
337
                variable_removed: false
1!
338
            }
×
339
        });
340
    };
1✔
341

342
    showDetected = () => {
343
        const { theme, variable, namespace, rjvId } = this.props;
1✔
344
        const { type, value } = this.state.parsedInput;
345
        const detected = this.getDetectedInput();
346
        if (detected) {
347
            return (
348
                <div>
349
                    <div {...Theme(theme, 'detected-row')}>
350
                        {detected}
351
                        <CheckCircle
352
                            class="edit-check detected"
353
                            style={{
354
                                verticalAlign: 'top',
355
                                paddingLeft: '3px',
356
                                ...Theme(theme, 'check-icon').style
87✔
357
                            }}
16✔
358
                            onClick={() => {
16✔
359
                                this.submitEdit(true);
16✔
360
                            }}
16✔
361
                        />
8✔
362
                    </div>
363
                </div>
364
            );
365
        }
366
    };
367

368
    getDetectedInput = () => {
369
        const { parsedInput } = this.state;
370
        const { type, value } = parsedInput;
371
        const { props } = this;
372
        const { theme } = props;
373

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

457
//export component
×
458
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