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]
);
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 (
<Wrapper>
<JsonTree
data={data}
rootName={name}
onFullyUpdate={onChange}
getStyle={getCustomStyleFunction(useTheme())}
cancelButtonElement={<Button type="button">Cancel</Button>}
editButtonElement={<Button type="submit">Save</Button>}
addButtonElement={
<Button type="submit" primary>
Save
</Button>
}
plusMenuElement={<ActionIcon icon="add" />}
minusMenuElement={<ActionIcon icon="subtract" />}
inputElement={(_: any, __: any, ___: any, key: string) =>
key ? <Input onFocus={selectValue} onBlur={dispatchEnterKey} /> : <Input />
}
fallback={
<Form.Textarea
id={name}
name={name}
defaultValue={value}
onBlur={(event) => updateRaw(event.target.value)}
size="flex"
placeholder="Enter JSON string"
valid={parseError ? 'error' : null}
/>
}
/>
{data ? (
<JsonTree
data={data}
rootName={name}
onFullyUpdate={onChange}
getStyle={getCustomStyleFunction(useTheme())}
cancelButtonElement={<Button type="button">Cancel</Button>}
editButtonElement={<Button type="submit">Save</Button>}
addButtonElement={
<Button type="submit" primary>
Save
</Button>
}
plusMenuElement={<ActionIcon icon="add" />}
minusMenuElement={<ActionIcon icon="subtract" />}
inputElement={(_: any, __: any, ___: any, key: string) =>
key ? <Input onFocus={selectValue} onBlur={dispatchEnterKey} /> : <Input />
}
fallback={rawJSONForm}
/>
) : (
rawJSONForm
)}
</Wrapper>
);
};

View File

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

View File

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

View File

@ -32,9 +32,9 @@ class JsonValue extends Component {
componentDidUpdate() {
const { editEnabled, inputRef, name, value, keyPath, deep } = this.state;
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();
}
}
@ -114,65 +114,54 @@ class JsonValue extends Component {
} = this.props;
const style = getStyle(name, originalValue, keyPath, deep, dataType);
let result = null;
let minusElement = null;
const readOnlyResult = readOnly(name, originalValue, keyPath, deep, dataType);
const isReadOnly = readOnly(name, originalValue, keyPath, deep, dataType);
const isEditing = editEnabled && !isReadOnly;
const inputElement = inputElementGenerator(
inputUsageTypes.VALUE,
comeFromKeyPath,
deep,
name,
originalValue,
dataType
);
if (editEnabled && !readOnlyResult) {
const inputElement = inputElementGenerator(
inputUsageTypes.VALUE,
comeFromKeyPath,
deep,
name,
originalValue,
dataType
);
const editButtonElementLayout = React.cloneElement(editButtonElement, {
onClick: this.handleEdit,
});
const cancelButtonElementLayout = React.cloneElement(cancelButtonElement, {
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;
}
const editButtonElementLayout = React.cloneElement(editButtonElement, {
onClick: this.handleEdit,
});
const cancelButtonElementLayout = React.cloneElement(cancelButtonElement, {
onClick: this.handleCancelEdit,
});
const inputElementLayout = React.cloneElement(inputElement, {
ref: this.refInput,
defaultValue: JSON.stringify(originalValue),
});
const minusMenuLayout = React.cloneElement(minusMenuElement, {
onClick: handleRemove,
className: 'rejt-minus-menu',
style: style.minus,
});
return (
<li className="rejt-value-node" style={style.li}>
<span className="rejt-name" style={style.name}>
{name} :{' '}
{name}
{' : '}
</span>
{result}
{minusElement}
{isEditing ? (
<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>
);
}

View File

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

View File

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