mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 01:12:56 +03:00
parent
2cdb1a1508
commit
71557cdecc
137
console/src/components/Common/CustomInputTypes/JsonInput.js
Normal file
137
console/src/components/Common/CustomInputTypes/JsonInput.js
Normal file
@ -0,0 +1,137 @@
|
||||
import React, { useState } from 'react';
|
||||
import AceEditor from 'react-ace';
|
||||
|
||||
const styles = require('./JsonInput.scss');
|
||||
|
||||
const NORMALKEY = 'normal';
|
||||
const JSONKEY = 'json';
|
||||
|
||||
const parseJSONData = (data, editorType) => {
|
||||
try {
|
||||
const dataObject = typeof data === 'object' ? data : JSON.parse(data);
|
||||
|
||||
return JSON.stringify(dataObject, null, editorType === JSONKEY ? 4 : 0);
|
||||
} catch (e) {
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
const createInitialState = data => {
|
||||
const initialState = {
|
||||
editorType: NORMALKEY,
|
||||
data: parseJSONData(data, NORMALKEY),
|
||||
};
|
||||
return initialState;
|
||||
};
|
||||
|
||||
const JsonInput = props => {
|
||||
const { standardProps, placeholderProp } = props;
|
||||
const { defaultValue, onChange } = standardProps;
|
||||
const allProps = { ...standardProps };
|
||||
delete allProps.defaultValue;
|
||||
const [state, updateState] = useState(createInitialState(defaultValue));
|
||||
const { editorType, data } = state;
|
||||
|
||||
const updateData = (newData, currentState) => {
|
||||
return {
|
||||
...currentState,
|
||||
data: newData,
|
||||
};
|
||||
};
|
||||
|
||||
const toggleEditorType = currentState => {
|
||||
const nextEditorType =
|
||||
currentState.editorType === JSONKEY ? NORMALKEY : JSONKEY;
|
||||
|
||||
return {
|
||||
...currentState,
|
||||
data: parseJSONData(currentState.data, nextEditorType),
|
||||
editorType: nextEditorType,
|
||||
};
|
||||
};
|
||||
|
||||
const handleKeyUpEvent = e => {
|
||||
if ((e.ctrlKey || event.metaKey) && e.which === 32) {
|
||||
updateState(toggleEditorType);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditorExec = () => {
|
||||
updateState(toggleEditorType);
|
||||
};
|
||||
|
||||
const handleInputChangeAndPropagate = e => {
|
||||
const val = e.target.value;
|
||||
updateState(currentState => updateData(val, currentState));
|
||||
if (onChange) {
|
||||
onChange(e);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTextAreaChangeAndPropagate = (value, e) => {
|
||||
const val = value;
|
||||
updateState(currentState => updateData(val, currentState));
|
||||
if (onChange) {
|
||||
onChange(e, value);
|
||||
}
|
||||
};
|
||||
|
||||
const getJsonEditor = () => {
|
||||
return (
|
||||
<AceEditor
|
||||
key="ace_json_editor"
|
||||
{...allProps}
|
||||
mode="json"
|
||||
theme="github"
|
||||
name="jsontoggler"
|
||||
minLines={10}
|
||||
maxLines={100}
|
||||
width="100%"
|
||||
value={data}
|
||||
showPrintMargin={false}
|
||||
onChange={handleTextAreaChangeAndPropagate}
|
||||
showGutter={false}
|
||||
focus
|
||||
commands={[
|
||||
{
|
||||
name: 'toggleEditor',
|
||||
bindKey: { win: 'Ctrl-Space', mac: 'Command-Space' },
|
||||
exec: handleEditorExec,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getNormalEditor = () => {
|
||||
return (
|
||||
<input
|
||||
key="input_json_editor"
|
||||
{...allProps}
|
||||
placeholder={`${placeholderProp} (Ctrl + Space to toggle)`}
|
||||
value={data}
|
||||
onChange={handleInputChangeAndPropagate}
|
||||
onKeyUp={handleKeyUpEvent}
|
||||
className={allProps.className + ' ' + styles.jsonNormalInput}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const editor = editorType === JSONKEY ? getJsonEditor() : getNormalEditor();
|
||||
|
||||
return (
|
||||
<span className="json_input_editor">
|
||||
<label>{editor}</label>
|
||||
<i
|
||||
key="icon_json_editor"
|
||||
className={
|
||||
'fa ' +
|
||||
styles.jsonToggleButton +
|
||||
(editorType === JSONKEY ? ' fa-compress' : ' fa-expand')
|
||||
}
|
||||
onClick={() => updateState(toggleEditorType)}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
export default JsonInput;
|
@ -0,0 +1,14 @@
|
||||
.jsonNormalInput {
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
.jsonToggleButton {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 100;
|
||||
opacity: 0.3;
|
||||
&:hover {
|
||||
opacity: 1.0;
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { getMeParentNode } from '../../../utils/domFunctions';
|
||||
import { getParentNodeByAttribute } from '../../../utils/domFunctions';
|
||||
import Button from '../Button/Button';
|
||||
|
||||
const styles = require('./Dropdown.scss');
|
||||
@ -65,7 +65,7 @@ const Dropdown = ({ keyPrefix, testId, children, options, position }) => {
|
||||
/*
|
||||
* Update the state only if the element clicked on is not the data dropdown component
|
||||
* */
|
||||
const dataElement = getMeParentNode(e.target, 'data-element');
|
||||
const dataElement = getParentNodeByAttribute(e.target, 'data-element');
|
||||
if (d) {
|
||||
/* If the element has parent whose `nodeId` is same as the current one
|
||||
* */
|
||||
|
@ -4,6 +4,7 @@ import TableHeader from '../TableCommon/TableHeader';
|
||||
import { editItem, E_ONGOING_REQ } from './EditActions';
|
||||
import globals from '../../../../Globals';
|
||||
import { modalClose } from './EditActions';
|
||||
import JsonInput from '../../../Common/CustomInputTypes/JsonInput';
|
||||
import Button from '../../../Common/Button/Button';
|
||||
|
||||
import {
|
||||
@ -14,8 +15,6 @@ import {
|
||||
DATE,
|
||||
BOOLEAN,
|
||||
UUID,
|
||||
JSONDTYPE,
|
||||
JSONB,
|
||||
TIMESTAMP,
|
||||
TIMETZ,
|
||||
} from '../utils';
|
||||
@ -167,16 +166,19 @@ class EditItem extends Component {
|
||||
data-test={`typed-input-${i}`}
|
||||
/>
|
||||
);
|
||||
} else if (colType === JSONDTYPE || colType === JSONB) {
|
||||
} else if (colType === 'json' || colType === 'jsonb') {
|
||||
const standardEditProps = {
|
||||
className: `form-control ${styles.insertBox}`,
|
||||
onClick: clicker,
|
||||
ref: inputRef,
|
||||
defaultValue: JSON.stringify(oldItem[colName]),
|
||||
'data-test': `typed-input-${i}`,
|
||||
type: 'text',
|
||||
};
|
||||
typedInput = (
|
||||
<input
|
||||
placeholder={getPlaceholder(colType)}
|
||||
type="text"
|
||||
className={'form-control ' + styles.insertBox}
|
||||
onClick={clicker}
|
||||
ref={inputRef}
|
||||
defaultValue={JSON.stringify(oldItem[colName])}
|
||||
data-test={`typed-input-${i}`}
|
||||
<JsonInput
|
||||
standardProps={standardEditProps}
|
||||
placeholderProp={'{"name": "foo"} or [12, "asdf"]'}
|
||||
/>
|
||||
);
|
||||
} else if (colType === BOOLEAN) {
|
||||
@ -350,7 +352,10 @@ class EditItem extends Component {
|
||||
// default
|
||||
return;
|
||||
} else {
|
||||
inputValues[colName] = refs[colName].valueNode.value; // TypedInput is an input inside a div
|
||||
inputValues[colName] =
|
||||
refs[colName].valueNode.props !== undefined
|
||||
? refs[colName].valueNode.props.value
|
||||
: refs[colName].valueNode.value;
|
||||
}
|
||||
});
|
||||
dispatch(editItem(tableName, inputValues));
|
||||
|
@ -4,9 +4,12 @@ import TableHeader from '../TableCommon/TableHeader';
|
||||
import { insertItem, I_RESET } from './InsertActions';
|
||||
import { ordinalColSort } from '../utils';
|
||||
import { setTable } from '../DataActions';
|
||||
import JsonInput from '../../../Common/CustomInputTypes/JsonInput';
|
||||
import Button from '../../../Common/Button/Button';
|
||||
import { getPlaceholder, BOOLEAN, JSONB, JSONDTYPE } from '../utils';
|
||||
|
||||
import { getParentNodeByClass } from '../../../../utils/domFunctions';
|
||||
|
||||
class InsertItem extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
@ -57,7 +60,12 @@ class InsertItem extends Component {
|
||||
refs[colName] = { valueNode: null, nullNode: null, defaultNode: null };
|
||||
const inputRef = node => (refs[colName].valueNode = node);
|
||||
const clicker = e => {
|
||||
e.target.parentNode.click();
|
||||
const checkboxLabel = getParentNodeByClass(e.target, 'radio-inline');
|
||||
if (checkboxLabel) {
|
||||
checkboxLabel.click();
|
||||
} else {
|
||||
e.target.parentNode.click();
|
||||
}
|
||||
e.target.focus();
|
||||
};
|
||||
const colDefault = col.column_default;
|
||||
@ -74,22 +82,21 @@ class InsertItem extends Component {
|
||||
'data-test': `typed-input-${i}`,
|
||||
defaultValue: clone && colName in clone ? clone[colName] : '',
|
||||
onClick: clicker,
|
||||
onChange: e => {
|
||||
onChange: (e, val) => {
|
||||
if (isAutoIncrement) return;
|
||||
if (!isNullable && !hasDefault) return;
|
||||
|
||||
const textValue = e.target.value;
|
||||
const textValue = typeof val === 'string' ? val : e.target.value;
|
||||
|
||||
const radioToSelectWhenEmpty = hasDefault
|
||||
? refs[colName].defaultNode
|
||||
: refs[colName].nullNode;
|
||||
|
||||
refs[colName].insertRadioNode.checked = !!textValue.length;
|
||||
radioToSelectWhenEmpty.checked = !textValue.length;
|
||||
},
|
||||
onFocus: e => {
|
||||
if (isAutoIncrement) return;
|
||||
if (!isNullable && !hasDefault) return;
|
||||
|
||||
const textValue = e.target.value;
|
||||
if (
|
||||
textValue === undefined ||
|
||||
@ -110,7 +117,6 @@ class InsertItem extends Component {
|
||||
};
|
||||
|
||||
const colType = col.data_type;
|
||||
|
||||
const placeHolder = hasDefault
|
||||
? col.column_default
|
||||
: getPlaceholder(colType);
|
||||
@ -128,12 +134,9 @@ class InsertItem extends Component {
|
||||
if (colType === JSONDTYPE || colType === JSONB) {
|
||||
// JSON/JSONB
|
||||
typedInput = (
|
||||
<input
|
||||
{...standardInputProps}
|
||||
placeholder={placeHolder}
|
||||
defaultValue={
|
||||
clone && colName in clone ? JSON.stringify(clone[colName]) : ''
|
||||
}
|
||||
<JsonInput
|
||||
standardProps={standardInputProps}
|
||||
placeholderProp={getPlaceholder(colType)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -262,7 +265,10 @@ class InsertItem extends Component {
|
||||
// default
|
||||
return;
|
||||
} else {
|
||||
inputValues[colName] = refs[colName].valueNode.value;
|
||||
inputValues[colName] =
|
||||
refs[colName].valueNode.props !== undefined
|
||||
? refs[colName].valueNode.props.value
|
||||
: refs[colName].valueNode.value;
|
||||
}
|
||||
});
|
||||
dispatch(insertItem(tableName, inputValues)).then(() => {
|
||||
|
@ -392,42 +392,24 @@ export const commonDataTypes = [
|
||||
description: 'autoincrementing four-byte integer',
|
||||
hasuraDatatype: null,
|
||||
},
|
||||
{
|
||||
name: 'UUID',
|
||||
value: 'uuid',
|
||||
description: 'universal unique identifier',
|
||||
hasuraDatatype: 'uuid',
|
||||
},
|
||||
{
|
||||
name: 'Big Integer',
|
||||
value: 'bigint',
|
||||
description: 'signed eight-byte integer',
|
||||
hasuraDatatype: 'bigint',
|
||||
},
|
||||
{
|
||||
name: 'Big Integer (auto-increment)',
|
||||
value: 'bigserial',
|
||||
description: 'autoincrementing eight-byte integer',
|
||||
hasuraDatatype: null,
|
||||
},
|
||||
{
|
||||
name: 'Text',
|
||||
value: 'text',
|
||||
description: 'variable-length character string',
|
||||
hasuraDatatype: 'text',
|
||||
},
|
||||
{
|
||||
name: 'Boolean',
|
||||
value: 'boolean',
|
||||
description: 'logical Boolean (true/false)',
|
||||
hasuraDatatype: 'boolean',
|
||||
},
|
||||
{
|
||||
name: 'Numeric',
|
||||
value: 'numeric',
|
||||
description: 'exact numeric of selected precision',
|
||||
hasuraDatatype: 'numeric',
|
||||
},
|
||||
{
|
||||
name: 'Date',
|
||||
value: 'date',
|
||||
description: 'calendar date (year, month, day)',
|
||||
hasuraDatatype: 'date',
|
||||
},
|
||||
{
|
||||
name: 'Timestamp',
|
||||
value: 'timestamptz',
|
||||
@ -441,10 +423,16 @@ export const commonDataTypes = [
|
||||
hasuraDatatype: 'time with time zone',
|
||||
},
|
||||
{
|
||||
name: 'Boolean',
|
||||
value: 'boolean',
|
||||
description: 'logical Boolean (true/false)',
|
||||
hasuraDatatype: 'boolean',
|
||||
name: 'Date',
|
||||
value: 'date',
|
||||
description: 'calendar date (year, month, day)',
|
||||
hasuraDatatype: 'date',
|
||||
},
|
||||
{
|
||||
name: 'UUID',
|
||||
value: 'uuid',
|
||||
description: 'universal unique identifier',
|
||||
hasuraDatatype: 'uuid',
|
||||
},
|
||||
{
|
||||
name: 'JSONB',
|
||||
@ -452,6 +440,18 @@ export const commonDataTypes = [
|
||||
description: 'binary format JSON data',
|
||||
hasuraDatatype: 'jsonb',
|
||||
},
|
||||
{
|
||||
name: 'Big Integer',
|
||||
value: 'bigint',
|
||||
description: 'signed eight-byte integer',
|
||||
hasuraDatatype: 'bigint',
|
||||
},
|
||||
{
|
||||
name: 'Big Integer (auto-increment)',
|
||||
value: 'bigserial',
|
||||
description: 'autoincrementing eight-byte integer',
|
||||
hasuraDatatype: null,
|
||||
},
|
||||
];
|
||||
|
||||
export const fetchColumnTypesQuery = `
|
||||
|
@ -8,22 +8,22 @@ export const convertListToDict = list => {
|
||||
const newList = list instanceof Array ? list : [].concat(list);
|
||||
return newList.length > 1
|
||||
? newList.reduce((prev, next) => {
|
||||
const accumulator =
|
||||
const accumulator =
|
||||
prev instanceof Object
|
||||
? prev
|
||||
: {
|
||||
[prev]: 1,
|
||||
};
|
||||
return {
|
||||
...accumulator,
|
||||
...{
|
||||
[next]: accumulator[next] ? accumulator[next] + 1 : 1,
|
||||
},
|
||||
};
|
||||
})
|
||||
: {
|
||||
[newList[0]]: 1,
|
||||
[prev]: 1,
|
||||
};
|
||||
return {
|
||||
...accumulator,
|
||||
...{
|
||||
[next]: accumulator[next] ? accumulator[next] + 1 : 1,
|
||||
},
|
||||
};
|
||||
})
|
||||
: {
|
||||
[newList[0]]: 1,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
@ -37,26 +37,26 @@ export const convertListToDictUsingKV = (keyName, keyValue, list) => {
|
||||
// if the list has more than one object then reduce it.
|
||||
return list.length > 1
|
||||
? list.reduce((prev, next, index) => {
|
||||
// Initial object for the list.
|
||||
if (index === 1) {
|
||||
const newObj = {};
|
||||
newObj[prev[keyName]] = prev[keyValue];
|
||||
newObj[next[keyName]] = next[keyValue];
|
||||
return newObj;
|
||||
}
|
||||
// Other objects for the list.
|
||||
prev[next[keyName]] = next[keyValue];
|
||||
return prev;
|
||||
})
|
||||
// Initial object for the list.
|
||||
if (index === 1) {
|
||||
const newObj = {};
|
||||
newObj[prev[keyName]] = prev[keyValue];
|
||||
newObj[next[keyName]] = next[keyValue];
|
||||
return newObj;
|
||||
}
|
||||
// Other objects for the list.
|
||||
prev[next[keyName]] = next[keyValue];
|
||||
return prev;
|
||||
})
|
||||
: (() => {
|
||||
return list.length <= 0
|
||||
? {}
|
||||
: (() => {
|
||||
const newObj = {};
|
||||
newObj[list[0][keyName]] = list[0][keyValue];
|
||||
return newObj;
|
||||
})();
|
||||
})();
|
||||
return list.length <= 0
|
||||
? {}
|
||||
: (() => {
|
||||
const newObj = {};
|
||||
newObj[list[0][keyName]] = list[0][keyValue];
|
||||
return newObj;
|
||||
})();
|
||||
})();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1,9 +1,18 @@
|
||||
/* Add error messages when trying to use the function */
|
||||
export const getMeParentNode = (node, selector) => {
|
||||
export const getParentNodeByAttribute = (node, selector) => {
|
||||
if (node && !node.documentElement) {
|
||||
return node.hasAttribute(selector)
|
||||
? node
|
||||
: getMeParentNode(node.parentNode, selector);
|
||||
: getParentNodeByAttribute(node.parentNode, selector);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const getParentNodeByClass = (node, selector) => {
|
||||
if (node && !node.documentElement) {
|
||||
return node.classList.contains(selector)
|
||||
? node
|
||||
: getParentNodeByClass(node.parentNode, selector);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user