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

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

28 Jul 2022 05:40AM UTC coverage: 75.419% (-7.9%) from 83.298%
#1374

push

SmitMaruti
update  styling

294 of 403 branches covered (72.95%)

Branch coverage included in aggregate %.

516 of 671 relevant lines covered (76.9%)

40.38 hits per line

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

58.56
/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 {
26
    Edit,
27
    CheckCircle,
28
    RemoveCircle as Remove,
29
    AddVariable
30
} from './icons';
31

32
//theme
33
import Theme from './../themes/getStyle';
34
import VariablePopup from './VariablePopup/VariablePopup';
35
import styled, { css } from 'styled-components';
36
const StyledKeyWrapper = styled.span`
1✔
37
    padding: 0 5px;
38
    &::before {
39
        content: '';
40
        clip-path: circle(50% at 100% 50%);
41
        aspect-ratio: 1 / 1;
42
        height: 100%;
43
        display: inline-block;
44
        position: absolute;
45
        left: 0;
46
        z-index: -1;
47
        transform: translateX(-100%);
48
    }
49
    &::after {
50
        content: '';
51
        aspect-ratio: 1 / 1;
52
        height: 100%;
53
        display: inline-block;
54
        position: absolute;
55
        z-index: -1;
56
        clip-path: circle(50% at 0 50%);
57
        right: 0;
58
        transform: translateX(100%);
59
    }
60
    z-index: 1;
61
    position: relative;
62
    display: inline-flex;
63
    ${props =>
64
        props.isHovered &&
76!
65
        css`
66
            background-color: #2a7cff29;
67
            &::after {
68
                background-color: #2a7cff29;
69
            }
70
            &::before {
71
                background-color: #2a7cff29;
72
            }
73
        `}
74
`;
75
class VariableEditor extends React.PureComponent {
76
    constructor(props) {
77
        super(props);
85✔
78
        this.state = {
85✔
79
            editMode: false,
80
            editValue: '',
81
            hovered: false,
82
            renameKey: false,
83
            parsedInput: {
84
                type: false,
85
                value: null
86
            },
87
            showPopup: false,
88
            popupPosition: {}
89
        };
90
    }
91

92
    render() {
93
        const {
94
            variable,
95
            singleIndent,
96
            type,
97
            theme,
98
            namespace,
99
            namespaceVariable,
100
            indentWidth,
101
            enableClipboard,
102
            onEdit,
103
            onDelete,
104
            onSelect,
105
            displayArrayKey,
106
            quotesOnKeys,
107
            onVariableAdd
108
        } = this.props;
120✔
109
        const { editMode } = this.state;
120✔
110
        return (
120✔
111
            <div
112
                {...Theme(theme, 'objectKeyVal', {
113
                    paddingLeft: indentWidth * singleIndent
114
                })}
115
                onMouseEnter={() =>
116
                    this.setState({ ...this.state, hovered: true })
×
117
                }
118
                onMouseLeave={() =>
119
                    this.setState({ ...this.state, hovered: false })
×
120
                }
121
                class="variable-row"
122
                key={variable.name}
123
            >
124
                {type == 'array' ? (
120✔
125
                    displayArrayKey ? (
26✔
126
                        <>
127
                            <StyledKeyWrapper
128
                                isHovered={
129
                                    this.state.hovered || this.state.showPopup
32✔
130
                                }
131
                            >
132
                                <span
133
                                    {...Theme(theme, 'array-key')}
134
                                    key={variable.name + '_' + namespace}
135
                                >
136
                                    {variable.name}
137
                                </span>
138
                                {(this.state.hovered ||
32!
139
                                    this.state.showPopup) && (
140
                                    <AddVariable
141
                                        style={{
142
                                            cursor: 'pointer',
143
                                            height: '1em',
144
                                            width: '1em',
145
                                            paddingLeft: '5px'
146
                                        }}
147
                                        handleClick={e => {
148
                                            this.setState({
×
149
                                                popupPosition: e.target.getBoundingClientRect(),
150
                                                showPopup: true
151
                                            });
152
                                        }}
153
                                    />
154
                                )}
155
                            </StyledKeyWrapper>
156
                            <div {...Theme(theme, 'colon')}>:</div>
157
                        </>
158
                    ) : null
159
                ) : (
160
                    <span>
161
                        <StyledKeyWrapper
162
                            isHovered={
163
                                this.state.hovered || this.state.showPopup
188✔
164
                            }
165
                        >
166
                            <span
167
                                {...Theme(theme, 'object-name')}
168
                                class="object-key"
169
                                key={variable.name + '_' + namespace}
170
                            >
171
                                {!!quotesOnKeys && (
145✔
172
                                    <span style={{ verticalAlign: 'top' }}>
173
                                        "
174
                                    </span>
175
                                )}
176
                                <span style={{ display: 'inline-block' }}>
177
                                    {variable.name}
178
                                </span>
179
                                {!!quotesOnKeys && (
145✔
180
                                    <span style={{ verticalAlign: 'top' }}>
181
                                        "
182
                                    </span>
183
                                )}
184
                            </span>
185
                            {(this.state.hovered || this.state.showPopup) && (
188!
186
                                <AddVariable
187
                                    style={{
188
                                        cursor: 'pointer',
189
                                        height: '1em',
190
                                        width: '1em',
191
                                        paddingLeft: '5px'
192
                                    }}
193
                                    handleClick={e => {
194
                                        this.setState({
×
195
                                            popupPosition: e.target.getBoundingClientRect(),
196
                                            showPopup: true
197
                                        });
198
                                    }}
199
                                />
200
                            )}
201
                        </StyledKeyWrapper>
202
                        <span {...Theme(theme, 'colon')}>:</span>
203
                    </span>
204
                )}
205
                <div
206
                    class="variable-value"
207
                    onClick={
208
                        onSelect === false && onEdit === false
299✔
209
                            ? null
210
                            : e => {
211
                                  let location = [...namespace];
×
212
                                  if (
×
213
                                      (e.ctrlKey || e.metaKey) &&
×
214
                                      onEdit !== false
215
                                  ) {
216
                                      this.prepopInput(variable);
×
217
                                  } else if (onSelect !== false) {
×
218
                                      location.shift();
×
219
                                      onSelect({
×
220
                                          ...variable,
221
                                          namespace: location
222
                                      });
223
                                  }
224
                              }
225
                    }
226
                    {...Theme(theme, 'variableValue', {
227
                        cursor: onSelect === false ? 'default' : 'pointer'
120✔
228
                    })}
229
                >
230
                    {this.getValue(variable, editMode)}
231
                </div>
232
                {enableClipboard ? (
120✔
233
                    <CopyToClipboard
234
                        rowHovered={this.state.hovered}
235
                        hidden={editMode}
236
                        src={variable.value}
237
                        clickCallback={enableClipboard}
238
                        {...{ theme, namespace: [...namespace, variable.name] }}
239
                    />
240
                ) : null}
241
                {onEdit !== false && editMode == false
300✔
242
                    ? this.getEditIcon()
243
                    : null}
244
                {onDelete !== false && editMode == false
301✔
245
                    ? this.getRemoveIcon()
246
                    : null}
247
                {this.state.showPopup && (
120!
248
                    <VariablePopup
249
                        position={this.state.popupPosition}
250
                        onClickAway={() => {
251
                            this.setState({
×
252
                                popupPosition: {},
253
                                showPopup: false,
254
                                hovered: false
255
                            });
256
                        }}
257
                        onCancel={() => {
258
                            this.setState({
×
259
                                popupPosition: {},
260
                                showPopup: false,
261
                                hovered: false
262
                            });
263
                        }}
264
                        onSubmit={key => {
265
                            let str = '';
×
266
                            let prevType = namespaceVariable[0].type;
×
267
                            namespaceVariable.forEach((item, index) => {
×
268
                                str +=
×
269
                                    item.type === 'object'
×
270
                                        ? `${item.name}.`
271
                                        : `${item.name}[`;
272
                                if (
×
273
                                    prevType === 'array' &&
×
274
                                    item.type === 'object'
275
                                ) {
276
                                    str = str.substring(0, str.length - 1);
×
277
                                    str += '].';
×
278
                                } else if (
×
279
                                    prevType === 'array' &&
×
280
                                    item.type === 'array'
281
                                ) {
282
                                    str = str.substring(0, str.length - 1);
×
283
                                    str += index === 0 ? '[' : '][';
×
284
                                } else if (prevType === 'array') str += ']';
×
285
                                prevType = item.type;
×
286
                            });
287
                            str += variable.name;
×
288
                            if (prevType === 'array') str += ']';
×
289
                            onVariableAdd(key, str);
×
290
                            this.setState({
×
291
                                popupPosition: {},
292
                                showPopup: false,
293
                                hovered: false
294
                            });
295
                        }}
296
                    />
297
                )}
298
            </div>
299
        );
300
    }
301

302
    getEditIcon = () => {
85✔
303
        const { variable, theme } = this.props;
44✔
304

305
        return (
44✔
306
            <div
307
                class="click-to-edit"
308
                style={{
309
                    verticalAlign: 'top',
310
                    display: this.state.hovered ? 'inline-block' : 'none'
44!
311
                }}
312
            >
313
                <Edit
314
                    class="click-to-edit-icon"
315
                    {...Theme(theme, 'editVarIcon')}
316
                    onClick={() => {
317
                        this.prepopInput(variable);
13✔
318
                    }}
319
                />
320
            </div>
321
        );
322
    };
323

324
    prepopInput = variable => {
85✔
325
        if (this.props.onEdit !== false) {
13!
326
            const stringifiedValue = stringifyVariable(variable.value);
13✔
327
            const detected = parseInput(stringifiedValue);
13✔
328
            this.setState({
13✔
329
                editMode: true,
330
                editValue: stringifiedValue,
331
                parsedInput: {
332
                    type: detected.type,
333
                    value: detected.value
334
                }
335
            });
336
        }
337
    };
338

339
    getRemoveIcon = () => {
85✔
340
        const { variable, namespace, theme, rjvId } = this.props;
45✔
341

342
        return (
45✔
343
            <div
344
                class="click-to-remove"
345
                style={{
346
                    verticalAlign: 'top',
347
                    display: this.state.hovered ? 'inline-block' : 'none'
45!
348
                }}
349
            >
350
                <Remove
351
                    class="click-to-remove-icon"
352
                    {...Theme(theme, 'removeVarIcon')}
353
                    onClick={() => {
354
                        dispatcher.dispatch({
×
355
                            name: 'VARIABLE_REMOVED',
356
                            rjvId: rjvId,
357
                            data: {
358
                                name: variable.name,
359
                                namespace: namespace,
360
                                existing_value: variable.value,
361
                                variable_removed: true
362
                            }
363
                        });
364
                    }}
365
                />
366
            </div>
367
        );
368
    };
369

370
    getValue = (variable, editMode) => {
85✔
371
        const type = editMode ? false : variable.type;
120✔
372
        const { props } = this;
120✔
373
        switch (type) {
120!
374
            case false:
375
                return this.getEditInput();
16✔
376
            case 'string':
377
                return <JsonString value={variable.value} {...props} />;
41✔
378
            case 'integer':
379
                return <JsonInteger value={variable.value} {...props} />;
24✔
380
            case 'float':
381
                return <JsonFloat value={variable.value} {...props} />;
2✔
382
            case 'boolean':
383
                return <JsonBoolean value={variable.value} {...props} />;
9✔
384
            case 'function':
385
                return <JsonFunction value={variable.value} {...props} />;
5✔
386
            case 'null':
387
                return <JsonNull {...props} />;
5✔
388
            case 'nan':
389
                return <JsonNan {...props} />;
5✔
390
            case 'undefined':
391
                return <JsonUndefined {...props} />;
2✔
392
            case 'date':
393
                return <JsonDate value={variable.value} {...props} />;
×
394
            case 'regexp':
395
                return <JsonRegexp value={variable.value} {...props} />;
4✔
396
            default:
397
                // catch-all for types that weren't anticipated
398
                return (
7✔
399
                    <div class="object-value">
400
                        {JSON.stringify(variable.value)}
401
                    </div>
402
                );
403
        }
404
    };
405

406
    getEditInput = () => {
85✔
407
        const { theme } = this.props;
16✔
408
        const { editValue } = this.state;
16✔
409

410
        return (
16✔
411
            <div>
412
                <AutosizeTextarea
413
                    type="text"
414
                    inputRef={input => input && input.focus()}
×
415
                    value={editValue}
416
                    class="variable-editor"
417
                    onChange={event => {
418
                        const value = event.target.value;
×
419
                        const detected = parseInput(value);
×
420
                        this.setState({
×
421
                            editValue: value,
422
                            parsedInput: {
423
                                type: detected.type,
424
                                value: detected.value
425
                            }
426
                        });
427
                    }}
428
                    onKeyDown={e => {
429
                        switch (e.key) {
×
430
                            case 'Escape': {
431
                                this.setState({
×
432
                                    editMode: false,
433
                                    editValue: ''
434
                                });
435
                                break;
×
436
                            }
437
                            case 'Enter': {
438
                                if (e.ctrlKey || e.metaKey) {
×
439
                                    this.submitEdit(true);
×
440
                                }
441
                                break;
×
442
                            }
443
                        }
444
                        e.stopPropagation();
×
445
                    }}
446
                    placeholder="update this value"
447
                    minRows={2}
448
                    {...Theme(theme, 'edit-input')}
449
                />
450
                <div {...Theme(theme, 'edit-icon-container')}>
451
                    <Remove
452
                        class="edit-cancel"
453
                        {...Theme(theme, 'cancel-icon')}
454
                        onClick={() => {
455
                            this.setState({ editMode: false, editValue: '' });
2✔
456
                        }}
457
                    />
458
                    <CheckCircle
459
                        class="edit-check string-value"
460
                        {...Theme(theme, 'check-icon')}
461
                        onClick={() => {
462
                            this.submitEdit();
1✔
463
                        }}
464
                    />
465
                    <div>{this.showDetected()}</div>
466
                </div>
467
            </div>
468
        );
469
    };
470

471
    submitEdit = submit_detected => {
85✔
472
        const { variable, namespace, rjvId } = this.props;
1✔
473
        const { editValue, parsedInput } = this.state;
1✔
474
        let new_value = editValue;
1✔
475
        if (submit_detected && parsedInput.type) {
1!
476
            new_value = parsedInput.value;
×
477
        }
478
        this.setState({
1✔
479
            editMode: false
480
        });
481
        dispatcher.dispatch({
1✔
482
            name: 'VARIABLE_UPDATED',
483
            rjvId: rjvId,
484
            data: {
485
                name: variable.name,
486
                namespace: namespace,
487
                existing_value: variable.value,
488
                new_value: new_value,
489
                variable_removed: false
490
            }
491
        });
492
    };
493

494
    showDetected = () => {
85✔
495
        const { theme, variable, namespace, rjvId } = this.props;
16✔
496
        const { type, value } = this.state.parsedInput;
16✔
497
        const detected = this.getDetectedInput();
16✔
498
        if (detected) {
16✔
499
            return (
8✔
500
                <div>
501
                    <div {...Theme(theme, 'detected-row')}>
502
                        {detected}
503
                        <CheckCircle
504
                            class="edit-check detected"
505
                            style={{
506
                                verticalAlign: 'top',
507
                                paddingLeft: '3px',
508
                                ...Theme(theme, 'check-icon').style
509
                            }}
510
                            onClick={() => {
511
                                this.submitEdit(true);
×
512
                            }}
513
                        />
514
                    </div>
515
                </div>
516
            );
517
        }
518
    };
519

520
    getDetectedInput = () => {
85✔
521
        const { parsedInput } = this.state;
16✔
522
        const { type, value } = parsedInput;
16✔
523
        const { props } = this;
16✔
524
        const { theme } = props;
16✔
525

526
        if (type !== false) {
16✔
527
            switch (type.toLowerCase()) {
8!
528
                case 'object':
529
                    return (
1✔
530
                        <span>
531
                            <span
532
                                style={{
533
                                    ...Theme(theme, 'brace').style,
534
                                    cursor: 'default'
535
                                }}
536
                            >
537
                                {'{'}
538
                            </span>
539
                            <span
540
                                style={{
541
                                    ...Theme(theme, 'ellipsis').style,
542
                                    cursor: 'default'
543
                                }}
544
                            >
545
                                ...
546
                            </span>
547
                            <span
548
                                style={{
549
                                    ...Theme(theme, 'brace').style,
550
                                    cursor: 'default'
551
                                }}
552
                            >
553
                                {'}'}
554
                            </span>
555
                        </span>
556
                    );
557
                case 'array':
558
                    return (
1✔
559
                        <span>
560
                            <span
561
                                style={{
562
                                    ...Theme(theme, 'brace').style,
563
                                    cursor: 'default'
564
                                }}
565
                            >
566
                                {'['}
567
                            </span>
568
                            <span
569
                                style={{
570
                                    ...Theme(theme, 'ellipsis').style,
571
                                    cursor: 'default'
572
                                }}
573
                            >
574
                                ...
575
                            </span>
576
                            <span
577
                                style={{
578
                                    ...Theme(theme, 'brace').style,
579
                                    cursor: 'default'
580
                                }}
581
                            >
582
                                {']'}
583
                            </span>
584
                        </span>
585
                    );
586
                case 'string':
587
                    return <JsonString value={value} {...props} />;
×
588
                case 'integer':
589
                    return <JsonInteger value={value} {...props} />;
2✔
590
                case 'float':
591
                    return <JsonFloat value={value} {...props} />;
1✔
592
                case 'boolean':
593
                    return <JsonBoolean value={value} {...props} />;
×
594
                case 'function':
595
                    return <JsonFunction value={value} {...props} />;
×
596
                case 'null':
597
                    return <JsonNull {...props} />;
1✔
598
                case 'nan':
599
                    return <JsonNan {...props} />;
1✔
600
                case 'undefined':
601
                    return <JsonUndefined {...props} />;
1✔
602
                case 'date':
603
                    return <JsonDate value={new Date(value)} {...props} />;
×
604
            }
605
        }
606
    };
607
}
608

609
//export component
610
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