diff --git a/README.md b/README.md index 560e475..99ffbb4 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ import ReactJsonView from '@microlink/react-json-view' | `escapeStrings` | `boolean` | `true` | When set to `true`, strings sequences such as \n, \t, \r, \f will be escaped. | | `bigNumber` | `Class` | `null` | A custom class for handling large numbers. The class should have a constructor that accepts a numeric string/value and a `name` property for display purposes. You can use existing libraries like `bignumber.js`, `decimal.js`, `big.js`, or provide your own implementation. | | `showComma` | `boolean` | `true` | When set to `true`, commas are displayed between object properties and array elements for better readability. Interactive tools (clipboard, edit, delete icons) appear after the comma when hovering over JSON elements. | +| `onEditAllowKeys` | `array` | `false` | When provided, only keys included in onEditAllowKeys will display the edit icon. If onEditAllowKeys.includes(key) is true, the edit icon will be shown. | #### Callbacks diff --git a/dev-server/src/index.js b/dev-server/src/index.js index 9fee0bc..9e50152 100644 --- a/dev-server/src/index.js +++ b/dev-server/src/index.js @@ -1,22 +1,22 @@ -'use strict' +"use strict"; //import react and reactDom for browser rendering -import React from 'react' -import ReactDom from 'react-dom' +import React from "react"; +import ReactDom from "react-dom"; -import Moment from 'moment' +import Moment from "moment"; //import the react-json-view component (installed with npm) -import JsonViewer from './../../src/js/index' +import JsonViewer from "./../../src/js/index"; // custom big number class, You can use existing libraries like `bignumber.js`, `decimal.js`, `big.js` etc. class BigNumber { - name = 'customName' + name = "customName"; constructor(value) { - this.value = value + this.value = value; } toString() { - return this.value.toString() + return this.value.toString(); } } @@ -27,44 +27,45 @@ ReactDom.render( { - console.log('edit callback', e) - if (e.new_value == 'error') { - return false + onEdit={(e) => { + console.log("edit callback", e); + if (e.new_value == "error") { + return false; } }} - onDelete={e => { - console.log('delete callback', e) + onDelete={(e) => { + console.log("delete callback", e); }} - onAdd={e => { - console.log('add callback', e) - if (e.new_value == 'error') { - return false + onAdd={(e) => { + console.log("add callback", e); + if (e.new_value == "error") { + return false; } }} - onSelect={e => { - console.log('select callback', e) - console.log(e.namespace) + onSelect={(e) => { + console.log("select callback", e); + console.log(e.namespace); }} displayObjectSize={true} - name={'dev-server'} - enableClipboard={copy => { - console.log('you copied to clipboard!', copy) + name={"dev-server"} + enableClipboard={(copy) => { + console.log("you copied to clipboard!", copy); }} shouldCollapse={({ src, namespace, type }) => { - if (type === 'array' && src.indexOf('test') > -1) { - return true - } else if (namespace.indexOf('moment') > -1) { - return true + if (type === "array" && src.indexOf("test") > -1) { + return true; + } else if (namespace.indexOf("moment") > -1) { + return true; } - return false + return false; }} - defaultValue='' + defaultValue="" showComma={true} + onEditAllowKeys={["string"]} />
@@ -73,43 +74,43 @@ ReactDom.render( { - console.log('edit callback', e) - if (e.new_value == 'error') { - return false + onEdit={(e) => { + console.log("edit callback", e); + if (e.new_value == "error") { + return false; } }} - onDelete={e => { - console.log('delete callback', e) + onDelete={(e) => { + console.log("delete callback", e); }} - onAdd={e => { - console.log('add callback', e) - if (e.new_value == 'error') { - return false + onAdd={(e) => { + console.log("add callback", e); + if (e.new_value == "error") { + return false; } }} - onSelect={e => { - console.log('select callback', e) - console.log(e.namespace) + onSelect={(e) => { + console.log("select callback", e); + console.log(e.namespace); }} displayObjectSize={true} - name={'dev-server (no commas)'} - enableClipboard={copy => { - console.log('you copied to clipboard!', copy) + name={"dev-server (no commas)"} + enableClipboard={(copy) => { + console.log("you copied to clipboard!", copy); }} shouldCollapse={({ src, namespace, type }) => { - if (type === 'array' && src.indexOf('test') > -1) { - return true - } else if (namespace.indexOf('moment') > -1) { - return true + if (type === "array" && src.indexOf("test") > -1) { + return true; + } else if (namespace.indexOf("moment") > -1) { + return true; } - return false + return false; }} - defaultValue='' + defaultValue="" showComma={false} /> @@ -119,30 +120,30 @@ ReactDom.render( { - console.log(e) - if (e.new_value === 'error') { - return false + onEdit={(e) => { + console.log(e); + if (e.new_value === "error") { + return false; } }} - onDelete={e => { - console.log(e) + onDelete={(e) => { + console.log(e); }} - onAdd={e => { - console.log(e) - if (e.new_value === 'error') { - return false + onAdd={(e) => { + console.log(e); + if (e.new_value === "error") { + return false; } }} name={false} - iconStyle='triangle' + iconStyle="triangle" shouldCollapse={({ src, type }) => - type === 'object' && + type === "object" && src.constructor && - src.constructor.name === 'Moment' + src.constructor.name === "Moment" } selectOnFocus showComma={true} @@ -154,7 +155,7 @@ ReactDom.render( @@ -165,7 +166,7 @@ ReactDom.render( @@ -176,7 +177,7 @@ ReactDom.render( { - console.log(edit) + theme="solarized" + onEdit={(edit) => { + console.log(edit); }} /> @@ -202,32 +203,32 @@ ReactDom.render( src={getExampleJson1()} bigNumber={BigNumber} shouldCollapse={({ src, namespace, type }) => - namespace.indexOf('moment') > -1 + namespace.indexOf("moment") > -1 } theme={{ - base00: 'white', - base01: '#ddd', - base02: '#ddd', - base03: '#444', - base04: 'purple', - base05: '#444', - base06: '#444', - base07: '#444', - base08: '#444', - base09: 'rgba(70, 70, 230, 1)', - base0A: 'rgba(70, 70, 230, 1)', - base0B: 'rgba(70, 70, 230, 1)', - base0C: 'rgba(70, 70, 230, 1)', - base0D: 'rgba(70, 70, 230, 1)', - base0E: 'rgba(70, 70, 230, 1)', - base0F: 'rgba(70, 70, 230, 1)' + base00: "white", + base01: "#ddd", + base02: "#ddd", + base03: "#444", + base04: "purple", + base05: "#444", + base06: "#444", + base07: "#444", + base08: "#444", + base09: "rgba(70, 70, 230, 1)", + base0A: "rgba(70, 70, 230, 1)", + base0B: "rgba(70, 70, 230, 1)", + base0C: "rgba(70, 70, 230, 1)", + base0D: "rgba(70, 70, 230, 1)", + base0E: "rgba(70, 70, 230, 1)", + base0F: "rgba(70, 70, 230, 1)", }} /> @@ -236,7 +237,7 @@ ReactDom.render( + React Element as name } @@ -245,126 +246,128 @@ ReactDom.render( {/* String with special escape sequences */} , - document.getElementById('app-container') -) + document.getElementById("app-container") +); /*-------------------------------------------------------------------------*/ /* the following functions just contain test json data for display */ /*-------------------------------------------------------------------------*/ //just a function to get an example JSON object -function getExampleJson1 () { +function getExampleJson1() { return { - string: 'this is a test string', + string: "this is a test string", integer: 42, empty_array: [], empty_object: {}, - array: [1, 2, 3, 'test'], + array: [1, 2, 3, "test"], float: -2.757, undefined_var: undefined, parent: { sibling1: true, sibling2: false, sibling3: null, - isString: value => { - if (typeof value === 'string') { - return 'string' + isString: (value) => { + if (typeof value === "string") { + return "string"; } else { - return 'other' + return "other"; } - } + }, }, - string_number: '1234', + string_number: "1234", date: new Date(), moment: Moment(), regexp: /[0-9]/gi, - bigNumber: new BigNumber('0.0060254656709730629123') - } + bigNumber: new BigNumber("0.0060254656709730629123"), + }; } //and another a function to get an example JSON object -function getExampleJson2 () { +function getExampleJson2() { return { normalized: { - '1-grams': { + "1-grams": { body: 1, - testing: 1 + testing: 1, }, - '2-grams': { - 'testing body': 1 + "2-grams": { + "testing body": 1, }, - '3-grams': {} + "3-grams": {}, }, noun_phrases: { - body: 1 + body: 1, }, lemmatized: { - '1-grams': { + "1-grams": { test: 1, - body: 1 + body: 1, }, - '2-grams': { - 'test body': 1 + "2-grams": { + "test body": 1, }, - '3-grams': {} + "3-grams": {}, }, dependency: { - '1-grams': { + "1-grams": { testingVERBROOTtestingVERB: 1, - bodyNOUNdobjtestingVERB: 1 + bodyNOUNdobjtestingVERB: 1, }, - '2-grams': { - 'testingVERBROOTtestingVERB bodyNOUNdobjtestingVERB': 1 + "2-grams": { + "testingVERBROOTtestingVERB bodyNOUNdobjtestingVERB": 1, }, - '3-grams': {} - } - } + "3-grams": {}, + }, + }; } -function getExampleJson3 () { +function getExampleJson3() { return { example_information: - 'this example has the collapsed prop set to true and the indentWidth prop is set to 8', + "this example has the collapsed prop set to true and the indentWidth prop is set to 8", default_collapsed: true, collapsed_array: [ - 'you expanded me', - 'try collapsing and expanding the root node', - 'i will still be expanded', + "you expanded me", + "try collapsing and expanding the root node", + "i will still be expanded", { - leaf_node: true - } - ] - } + leaf_node: true, + }, + ], + }; } -function getExampleJson4 () { - const large_array = new Array(225).fill('this is a large array full of items') +function getExampleJson4() { + const large_array = new Array(225).fill( + "this is a large array full of items" + ); - large_array.push(getExampleArray()) + large_array.push(getExampleArray()); - large_array.push(new Array(75).fill(Math.random())) + large_array.push(new Array(75).fill(Math.random())); - return large_array + return large_array; } -function getExampleArray () { +function getExampleArray() { return [ - 'you can also display arrays!', + "you can also display arrays!", new Date(), 1, 2, 3, { - pretty_cool: true - } - ] + pretty_cool: true, + }, + ]; } -function getExampleWithStringEscapeSequences () { - return { '\\\n\t\r\f\\n': '\\\n\t\r\f\\n' } +function getExampleWithStringEscapeSequences() { + return { "\\\n\t\r\f\\n": "\\\n\t\r\f\\n" }; } diff --git a/src/js/components/VariableEditor.js b/src/js/components/VariableEditor.js index 2d14b10..8cf2d0c 100644 --- a/src/js/components/VariableEditor.js +++ b/src/js/components/VariableEditor.js @@ -1,11 +1,11 @@ -import React from 'react' -import AutosizeTextarea from 'react-textarea-autosize' +import React from "react"; +import AutosizeTextarea from "react-textarea-autosize"; -import { escapeString } from './../helpers/util' -import dispatcher from './../helpers/dispatcher' -import parseInput from './../helpers/parseInput' -import stringifyVariable from './../helpers/stringifyVariable' -import CopyToClipboard from './CopyToClipboard' +import { escapeString } from "./../helpers/util"; +import dispatcher from "./../helpers/dispatcher"; +import parseInput from "./../helpers/parseInput"; +import stringifyVariable from "./../helpers/stringifyVariable"; +import CopyToClipboard from "./CopyToClipboard"; // data type components import { @@ -19,31 +19,31 @@ import { JsonRegexp, JsonString, JsonUndefined, - JsonBigNumber -} from './DataTypes/DataTypes' + JsonBigNumber, +} from "./DataTypes/DataTypes"; // clibboard icon -import { Edit, CheckCircle, RemoveCircle as Remove } from './icons' +import { Edit, CheckCircle, RemoveCircle as Remove } from "./icons"; // theme -import Theme from './../themes/getStyle' +import Theme from "./../themes/getStyle"; class VariableEditor extends React.PureComponent { - constructor (props) { - super(props) + constructor(props) { + super(props); this.state = { editMode: false, - editValue: '', + editValue: "", hovered: false, renameKey: false, parsedInput: { type: false, - value: null - } - } + value: null, + }, + }; } - render () { + render() { const { variable, singleIndent, @@ -59,422 +59,428 @@ class VariableEditor extends React.PureComponent { quotesOnKeys, keyModifier, showComma, - isLast - } = this.props - const { editMode } = this.state + isLast, + onEditAllowKeys, + } = this.props; + const { editMode } = this.state; + return (
this.setState({ ...this.state, hovered: true })} onMouseLeave={() => this.setState({ ...this.state, hovered: false })} - className='variable-row' + className="variable-row" key={variable.name} > - {type == 'array' - ? ( - displayArrayKey - ? ( - - {variable.name} -
:
-
- ) - : null - ) - : ( - - - {!!quotesOnKeys && ( - " - )} - {escapeString(variable.name)} - {!!quotesOnKeys && ( - " - )} + {type == "array" ? ( + displayArrayKey ? ( + + {variable.name} +
:
+
+ ) : null + ) : ( + + + {!!quotesOnKeys && ( + " + )} + + {escapeString(variable.name)} - : + {!!quotesOnKeys && ( + " + )} - )} + : + + )}
{ - const location = [...namespace] - if (keyModifier(e, 'edit') && onEdit !== false) { - this.prepopInput(variable) - } else if (onSelect !== false) { - location.shift() - onSelect({ - ...variable, - namespace: location - }) + : (e) => { + const location = [...namespace]; + if (keyModifier(e, "edit") && onEdit !== false) { + this.prepopInput(variable); + } else if (onSelect !== false) { + location.shift(); + onSelect({ + ...variable, + namespace: location, + }); + } } - } } - {...Theme(theme, 'variableValue', { - cursor: onSelect === false ? 'default' : 'pointer' + {...Theme(theme, "variableValue", { + cursor: onSelect === false ? "default" : "pointer", })} > {this.getValue(variable, editMode)}
- {showComma && !isLast && ( - - , - - )} - {enableClipboard - ? ( -
- ) + ); } getEditIcon = () => { - const { variable, theme } = this.props + const { variable, theme } = this.props; return (
{ - this.prepopInput(variable) + this.prepopInput(variable); }} />
- ) - } + ); + }; - prepopInput = variable => { + prepopInput = (variable) => { if (this.props.onEdit !== false) { - const stringifiedValue = stringifyVariable(variable.value, this.props.bigNumber) - const detected = parseInput(stringifiedValue, this.props.bigNumber) + const stringifiedValue = stringifyVariable( + variable.value, + this.props.bigNumber + ); + const detected = parseInput(stringifiedValue, this.props.bigNumber); this.setState({ editMode: true, editValue: stringifiedValue, parsedInput: { type: detected.type, - value: detected.value - } - }) + value: detected.value, + }, + }); } - } + }; getRemoveIcon = () => { - const { variable, namespace, theme, rjvId } = this.props + const { variable, namespace, theme, rjvId } = this.props; return (
{ dispatcher.dispatch({ - name: 'VARIABLE_REMOVED', + name: "VARIABLE_REMOVED", rjvId, data: { name: variable.name, namespace, existing_value: variable.value, - variable_removed: true - } - }) + variable_removed: true, + }, + }); }} />
- ) - } + ); + }; getValue = (variable, editMode) => { - const type = editMode ? false : variable.type - const { props } = this + const type = editMode ? false : variable.type; + const { props } = this; switch (type) { case false: - return this.getEditInput() - case 'string': - return - case 'integer': - return - case 'float': - return - case 'boolean': - return - case 'function': - return - case 'null': - return - case 'nan': - return - case 'undefined': - return - case 'date': - return - case 'regexp': - return - case 'bigNumber': - return + return this.getEditInput(); + case "string": + return ; + case "integer": + return ; + case "float": + return ; + case "boolean": + return ; + case "function": + return ; + case "null": + return ; + case "nan": + return ; + case "undefined": + return ; + case "date": + return ; + case "regexp": + return ; + case "bigNumber": + return ; default: // catch-all for types that weren't anticipated - return
{JSON.stringify(variable.value)}
+ return ( +
{JSON.stringify(variable.value)}
+ ); } - } + }; getEditInput = () => { - const { keyModifier, selectOnFocus, theme } = this.props - const { editValue } = this.state + const { keyModifier, selectOnFocus, theme } = this.props; + const { editValue } = this.state; return (
{ + type="text" + ref={(input) => { if (input) { - input[!selectOnFocus ? 'focus' : 'select']() + input[!selectOnFocus ? "focus" : "select"](); } }} value={editValue} - className='variable-editor' - onChange={event => { - const value = event.target.value - const detected = parseInput(value, this.props.bigNumber) + className="variable-editor" + onChange={(event) => { + const value = event.target.value; + const detected = parseInput(value, this.props.bigNumber); this.setState({ editValue: value, parsedInput: { type: detected.type, - value: detected.value - } - }) + value: detected.value, + }, + }); }} - onKeyDown={e => { + onKeyDown={(e) => { switch (e.key) { - case 'Escape': { + case "Escape": { this.setState({ editMode: false, - editValue: '' - }) - break + editValue: "", + }); + break; } - case 'Enter': { - if (keyModifier(e, 'submit')) { - this.submitEdit(true) + case "Enter": { + if (keyModifier(e, "submit")) { + this.submitEdit(true); } - break + break; } } - e.stopPropagation() + e.stopPropagation(); }} - placeholder='update this value' + placeholder="update this value" minRows={2} - {...Theme(theme, 'edit-input')} + {...Theme(theme, "edit-input")} /> -
+
{ + className="edit-cancel" + {...Theme(theme, "cancel-icon")} + onClick={(e) => { if (e) { - e.stopPropagation() + e.stopPropagation(); } - this.setState({ editMode: false, editValue: '' }) + this.setState({ editMode: false, editValue: "" }); }} /> { + className="edit-check string-value" + {...Theme(theme, "check-icon")} + onClick={(e) => { if (e) { - e.stopPropagation() + e.stopPropagation(); } - this.submitEdit() + this.submitEdit(); }} />
{this.showDetected()}
- ) - } + ); + }; - submitEdit = submit_detected => { - const { variable, namespace, rjvId, bigNumber: BigNumber } = this.props - const { editValue, parsedInput } = this.state - let new_value = editValue + submitEdit = (submit_detected) => { + const { variable, namespace, rjvId, bigNumber: BigNumber } = this.props; + const { editValue, parsedInput } = this.state; + let new_value = editValue; if (submit_detected && parsedInput.type) { - new_value = parsedInput.value - if (BigNumber && parsedInput.type === 'bigNumber') { - new_value = new BigNumber(new_value) + new_value = parsedInput.value; + if (BigNumber && parsedInput.type === "bigNumber") { + new_value = new BigNumber(new_value); } - } + } this.setState({ - editMode: false - }) + editMode: false, + }); dispatcher.dispatch({ - name: 'VARIABLE_UPDATED', + name: "VARIABLE_UPDATED", rjvId, data: { name: variable.name, namespace, existing_value: variable.value, new_value, - variable_removed: false - } - }) - } + variable_removed: false, + }, + }); + }; showDetected = () => { - const { theme, variable, namespace, rjvId } = this.props - const { type, value } = this.state.parsedInput - const detected = this.getDetectedInput() + const { theme, variable, namespace, rjvId } = this.props; + const { type, value } = this.state.parsedInput; + const detected = this.getDetectedInput(); if (detected) { return (
-
+
{detected} { + onClick={(e) => { if (e) { - e.stopPropagation() + e.stopPropagation(); } - this.submitEdit(true) + this.submitEdit(true); }} />
- ) + ); } - } + }; getDetectedInput = () => { - const { parsedInput } = this.state - const { type, value } = parsedInput - const { props } = this - const { theme } = props + const { parsedInput } = this.state; + const { type, value } = parsedInput; + const { props } = this; + const { theme } = props; if (type !== false) { switch (type.toLowerCase()) { - case 'object': + case "object": return ( - {'{'} + {"{"} ... - {'}'} + {"}"} - ) - case 'array': + ); + case "array": return ( [ ... ] - ) - case 'string': - return - case 'integer': - return - case 'float': - return - case 'boolean': - return - case 'function': - return - case 'null': - return - case 'nan': - return - case 'undefined': - return - case 'date': - return - case 'bignumber': - return + ); + case "string": + return ; + case "integer": + return ; + case "float": + return ; + case "boolean": + return ; + case "function": + return ; + case "null": + return ; + case "nan": + return ; + case "undefined": + return ; + case "date": + return ; + case "bignumber": + return ; } } - } + }; } // export component -export default VariableEditor +export default VariableEditor; diff --git a/src/js/index.js b/src/js/index.js index b2ed403..a4cda16 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -61,7 +61,8 @@ class ReactJsonView extends React.PureComponent { selectOnFocus: false, keyModifier: e => e.metaKey || e.ctrlKey, bigNumber: null, - showComma: true + showComma: true, + onEditAllowKeys: false } // will trigger whenever setState() is called, or parent passes in new props.