Support removing the root node

This commit is contained in:
Gert Hengeveld 2021-02-23 10:54:58 +01:00
parent 63d32842e6
commit 10dc235f9b
6 changed files with 91 additions and 94 deletions

View File

@ -207,38 +207,43 @@ export const ObjectControl: React.FC<ObjectProps> = ({ name, value = {}, onChang
}, },
[onChange] [onChange]
); );
const rawJSONForm = (
<Form.Textarea
id={name}
name={name}
defaultValue={value}
onBlur={(event) => updateRaw(event.target.value)}
size="flex"
placeholder="Enter JSON string"
valid={parseError ? 'error' : null}
/>
);
return ( return (
<Wrapper> <Wrapper>
<JsonTree {data ? (
data={data} <JsonTree
rootName={name} data={data}
onFullyUpdate={onChange} rootName={name}
getStyle={getCustomStyleFunction(useTheme())} onFullyUpdate={onChange}
cancelButtonElement={<Button type="button">Cancel</Button>} getStyle={getCustomStyleFunction(useTheme())}
editButtonElement={<Button type="submit">Save</Button>} cancelButtonElement={<Button type="button">Cancel</Button>}
addButtonElement={ editButtonElement={<Button type="submit">Save</Button>}
<Button type="submit" primary> addButtonElement={
Save <Button type="submit" primary>
</Button> Save
} </Button>
plusMenuElement={<ActionIcon icon="add" />} }
minusMenuElement={<ActionIcon icon="subtract" />} plusMenuElement={<ActionIcon icon="add" />}
inputElement={(_: any, __: any, ___: any, key: string) => minusMenuElement={<ActionIcon icon="subtract" />}
key ? <Input onFocus={selectValue} onBlur={dispatchEnterKey} /> : <Input /> inputElement={(_: any, __: any, ___: any, key: string) =>
} key ? <Input onFocus={selectValue} onBlur={dispatchEnterKey} /> : <Input />
fallback={ }
<Form.Textarea fallback={rawJSONForm}
id={name} />
name={name} ) : (
defaultValue={value} rawJSONForm
onBlur={(event) => updateRaw(event.target.value)} )}
size="flex"
placeholder="Enter JSON string"
valid={parseError ? 'error' : null}
/>
}
/>
</Wrapper> </Wrapper>
); );
}; };

View File

@ -166,7 +166,6 @@ class JsonArray extends Component {
const { handleRemove, readOnly, getStyle, dataType, minusMenuElement } = this.props; const { handleRemove, readOnly, getStyle, dataType, minusMenuElement } = this.props;
const { minus, collapsed } = getStyle(name, data, keyPath, deep, dataType); const { minus, collapsed } = getStyle(name, data, keyPath, deep, dataType);
const isRoot = deep === -1;
const isReadOnly = readOnly(name, data, keyPath, deep, dataType); const isReadOnly = readOnly(name, data, keyPath, deep, dataType);
const removeItemButton = React.cloneElement(minusMenuElement, { const removeItemButton = React.cloneElement(minusMenuElement, {
@ -181,7 +180,7 @@ class JsonArray extends Component {
<span className="rejt-collapsed-text" style={collapsed} onClick={this.handleCollapseMode}> <span className="rejt-collapsed-text" style={collapsed} onClick={this.handleCollapseMode}>
[...] {data.length} {data.length === 1 ? 'item' : 'items'} [...] {data.length} {data.length === 1 ? 'item' : 'items'}
</span> </span>
{!isRoot && !isReadOnly && removeItemButton} {!isReadOnly && removeItemButton}
</span> </span>
); );
/* eslint-enable */ /* eslint-enable */
@ -210,7 +209,7 @@ class JsonArray extends Component {
onSubmitValueParser, onSubmitValueParser,
} = this.props; } = this.props;
const { minus, plus, delimiter, ul, addForm } = getStyle(name, data, keyPath, deep, dataType); const { minus, plus, delimiter, ul, addForm } = getStyle(name, data, keyPath, deep, dataType);
const isRoot = deep === -1;
const isReadOnly = readOnly(name, data, keyPath, deep, dataType); const isReadOnly = readOnly(name, data, keyPath, deep, dataType);
const addItemButton = React.cloneElement(plusMenuElement, { const addItemButton = React.cloneElement(plusMenuElement, {
@ -281,7 +280,7 @@ class JsonArray extends Component {
<span className="rejt-not-collapsed-delimiter" style={delimiter}> <span className="rejt-not-collapsed-delimiter" style={delimiter}>
{endObject} {endObject}
</span> </span>
{!isReadOnly && !isRoot && removeItemButton} {!isReadOnly && removeItemButton}
</span> </span>
); );
} }

View File

@ -167,7 +167,6 @@ class JsonObject extends Component {
const { minus, collapsed } = getStyle(name, data, keyPath, deep, dataType); const { minus, collapsed } = getStyle(name, data, keyPath, deep, dataType);
const keyList = Object.getOwnPropertyNames(data); const keyList = Object.getOwnPropertyNames(data);
const isRoot = deep === -1;
const isReadOnly = readOnly(name, data, keyPath, deep, dataType); const isReadOnly = readOnly(name, data, keyPath, deep, dataType);
const removeItemButton = React.cloneElement(minusMenuElement, { const removeItemButton = React.cloneElement(minusMenuElement, {
@ -182,7 +181,7 @@ class JsonObject extends Component {
<span className="rejt-collapsed-text" style={collapsed} onClick={this.handleCollapseMode}> <span className="rejt-collapsed-text" style={collapsed} onClick={this.handleCollapseMode}>
{'{...}'} {keyList.length} {keyList.length === 1 ? 'key' : 'keys'} {'{...}'} {keyList.length} {keyList.length === 1 ? 'key' : 'keys'}
</span> </span>
{!isRoot && !isReadOnly && removeItemButton} {!isReadOnly && removeItemButton}
</span> </span>
); );
/* eslint-enable */ /* eslint-enable */
@ -214,7 +213,6 @@ class JsonObject extends Component {
const { minus, plus, addForm, ul, delimiter } = getStyle(name, data, keyPath, deep, dataType); const { minus, plus, addForm, ul, delimiter } = getStyle(name, data, keyPath, deep, dataType);
const keyList = Object.getOwnPropertyNames(data); const keyList = Object.getOwnPropertyNames(data);
const isRoot = deep === -1;
const isReadOnly = readOnly(name, data, keyPath, deep, dataType); const isReadOnly = readOnly(name, data, keyPath, deep, dataType);
const addItemButton = React.cloneElement(plusMenuElement, { const addItemButton = React.cloneElement(plusMenuElement, {
@ -286,7 +284,7 @@ class JsonObject extends Component {
<span className="rejt-not-collapsed-delimiter" style={delimiter}> <span className="rejt-not-collapsed-delimiter" style={delimiter}>
{endObject} {endObject}
</span> </span>
{!isRoot && !isReadOnly && removeItemButton} {!isReadOnly && removeItemButton}
</span> </span>
); );
} }

View File

@ -32,9 +32,9 @@ class JsonValue extends Component {
componentDidUpdate() { componentDidUpdate() {
const { editEnabled, inputRef, name, value, keyPath, deep } = this.state; const { editEnabled, inputRef, name, value, keyPath, deep } = this.state;
const { readOnly, dataType } = this.props; const { readOnly, dataType } = this.props;
const readOnlyResult = readOnly(name, value, keyPath, deep, dataType); const isReadOnly = readOnly(name, value, keyPath, deep, dataType);
if (editEnabled && !readOnlyResult && typeof inputRef.focus === 'function') { if (editEnabled && !isReadOnly && typeof inputRef.focus === 'function') {
inputRef.focus(); inputRef.focus();
} }
} }
@ -114,65 +114,54 @@ class JsonValue extends Component {
} = this.props; } = this.props;
const style = getStyle(name, originalValue, keyPath, deep, dataType); const style = getStyle(name, originalValue, keyPath, deep, dataType);
let result = null; const isReadOnly = readOnly(name, originalValue, keyPath, deep, dataType);
let minusElement = null; const isEditing = editEnabled && !isReadOnly;
const readOnlyResult = readOnly(name, originalValue, keyPath, deep, dataType); const inputElement = inputElementGenerator(
inputUsageTypes.VALUE,
comeFromKeyPath,
deep,
name,
originalValue,
dataType
);
if (editEnabled && !readOnlyResult) { const editButtonElementLayout = React.cloneElement(editButtonElement, {
const inputElement = inputElementGenerator( onClick: this.handleEdit,
inputUsageTypes.VALUE, });
comeFromKeyPath, const cancelButtonElementLayout = React.cloneElement(cancelButtonElement, {
deep, onClick: this.handleCancelEdit,
name, });
originalValue, const inputElementLayout = React.cloneElement(inputElement, {
dataType ref: this.refInput,
); defaultValue: JSON.stringify(originalValue),
});
const editButtonElementLayout = React.cloneElement(editButtonElement, { const minusMenuLayout = React.cloneElement(minusMenuElement, {
onClick: this.handleEdit, onClick: handleRemove,
}); className: 'rejt-minus-menu',
const cancelButtonElementLayout = React.cloneElement(cancelButtonElement, { style: style.minus,
onClick: this.handleCancelEdit, });
});
const inputElementLayout = React.cloneElement(inputElement, {
ref: this.refInput,
defaultValue: JSON.stringify(originalValue),
});
result = (
<span className="rejt-edit-form" style={style.editForm}>
{inputElementLayout} {cancelButtonElementLayout}
{editButtonElementLayout}
</span>
);
minusElement = null;
} else {
/* eslint-disable jsx-a11y/no-static-element-interactions */
result = (
<span
className="rejt-value"
style={style.value}
onClick={readOnlyResult ? null : this.handleEditMode}
>
{String(value)}
</span>
);
/* eslint-enable */
const minusMenuLayout = React.cloneElement(minusMenuElement, {
onClick: handleRemove,
className: 'rejt-minus-menu',
style: style.minus,
});
minusElement = readOnlyResult ? null : minusMenuLayout;
}
return ( return (
<li className="rejt-value-node" style={style.li}> <li className="rejt-value-node" style={style.li}>
<span className="rejt-name" style={style.name}> <span className="rejt-name" style={style.name}>
{name} :{' '} {name}
{' : '}
</span> </span>
{result} {isEditing ? (
{minusElement} <span className="rejt-edit-form" style={style.editForm}>
{inputElementLayout} {cancelButtonElementLayout}
{editButtonElementLayout}
</span>
) : (
<span
className="rejt-value"
style={style.value}
onClick={isReadOnly ? null : this.handleEditMode}
>
{String(value)}
</span>
)}
{!isReadOnly && !isEditing && minusMenuLayout}
</li> </li>
); );
} }

View File

@ -18,6 +18,7 @@ class JsonTree extends Component {
}; };
// Bind // Bind
this.onUpdate = this.onUpdate.bind(this); this.onUpdate = this.onUpdate.bind(this);
this.removeRoot = this.removeRoot.bind(this);
} }
static getDerivedStateFromProps(props, state) { static getDerivedStateFromProps(props, state) {
@ -35,6 +36,10 @@ class JsonTree extends Component {
this.props.onFullyUpdate(data); this.props.onFullyUpdate(data);
} }
removeRoot() {
this.onUpdate(null, null);
}
render() { render() {
const { data, rootName } = this.state; const { data, rootName } = this.state;
const { const {
@ -59,7 +64,7 @@ class JsonTree extends Component {
// Node type // Node type
const dataType = getObjectType(data); const dataType = getObjectType(data);
let node = null;
let readOnlyFunction = readOnly; let readOnlyFunction = readOnly;
if (getObjectType(readOnly) === 'Boolean') { if (getObjectType(readOnly) === 'Boolean') {
readOnlyFunction = () => readOnly; readOnlyFunction = () => readOnly;
@ -93,6 +98,7 @@ class JsonTree extends Component {
textareaElementGenerator={textareaElementFunction} textareaElementGenerator={textareaElementFunction}
minusMenuElement={minusMenuElement} minusMenuElement={minusMenuElement}
plusMenuElement={plusMenuElement} plusMenuElement={plusMenuElement}
handleRemove={this.removeRoot}
beforeRemoveAction={beforeRemoveAction} beforeRemoveAction={beforeRemoveAction}
beforeAddAction={beforeAddAction} beforeAddAction={beforeAddAction}
beforeUpdateAction={beforeUpdateAction} beforeUpdateAction={beforeUpdateAction}

View File

@ -41,7 +41,7 @@ const deepDiff = (value: any, update: any): any => {
if (deepEqual(value, update)) return undefined; if (deepEqual(value, update)) return undefined;
if (typeof value !== typeof update) return update; if (typeof value !== typeof update) return update;
if (Array.isArray(value)) return update; if (Array.isArray(value)) return update;
if (typeof update === 'object') { if (update && typeof update === 'object') {
return Object.keys(update).reduce((acc, key) => { return Object.keys(update).reduce((acc, key) => {
const diff = deepDiff(value[key], update[key]); const diff = deepDiff(value[key], update[key]);
return diff === undefined ? acc : Object.assign(acc, { [key]: diff }); return diff === undefined ? acc : Object.assign(acc, { [key]: diff });