add multiline and rich editing to text fields (close #458) (#2498)

This commit is contained in:
Anne Ogborn 2019-07-22 15:17:31 +05:30 committed by Rikin Kachhia
parent dde9908650
commit 7a683324f2
5 changed files with 281 additions and 211 deletions

View File

@ -1,8 +1,8 @@
.jsonNormalInput {
.normalInput {
padding-right: 30px;
}
.jsonToggleButton {
.modeToggleButton {
position: absolute;
top: 10px;
right: 10px;
@ -11,4 +11,12 @@
&:hover {
opacity: 1.0;
}
}
.modeType {
position: absolute;
top: 6px;
right: 28px;
z-index: 100;
font-style: italic;
}

View File

@ -1,7 +1,10 @@
import React, { useState } from 'react';
import AceEditor from 'react-ace';
const styles = require('./JsonInput.scss');
import 'brace/mode/markdown';
import 'brace/theme/github';
const styles = require('./CustomInput.scss');
const NORMALKEY = 'normal';
const JSONKEY = 'json';
@ -112,7 +115,7 @@ const JsonInput = props => {
value={data}
onChange={handleInputChangeAndPropagate}
onKeyUp={handleKeyUpEvent}
className={allProps.className + ' ' + styles.jsonNormalInput}
className={allProps.className + ' ' + styles.normalInput}
/>
);
};
@ -126,12 +129,12 @@ const JsonInput = props => {
key="icon_json_editor"
className={
'fa ' +
styles.jsonToggleButton +
styles.modeToggleButton +
(editorType === JSONKEY ? ' fa-compress' : ' fa-expand')
}
onClick={() => updateState(toggleEditorType)}
title={
(editorType === JSONKEY ? 'Collapse' : 'Expand') + '(Ctrl + Space)'
(editorType === JSONKEY ? 'Collapse' : 'Expand') + ' (Ctrl + Space)'
}
/>
</span>

View File

@ -0,0 +1,166 @@
import React, { useState } from 'react';
import AceEditor from 'react-ace';
import 'brace/mode/html';
import 'brace/mode/markdown';
import 'brace/theme/github';
import 'brace/theme/chrome';
const styles = require('./CustomInput.scss');
// editorType is what sort of editor. All are ACE Editor
// modes except 0, which is text input
// ACE editor mode names
const EDITORTYPES = [
'normal', // must be first
'text',
'markdown',
'html',
];
// human readable editor names
const EDITORTYPENAMES = [
'single line input',
'multi-line text input',
'markdown',
'html',
];
// short human readable editor names
// for the visible label
const SHORTEDITORTYPENAMES = ['', 'multi-line', 'markdown', 'html'];
const NORMALKEY = 0;
const createInitialState = data => {
const initialState = {
editorType: NORMALKEY,
data: data,
};
return initialState;
};
const TextInput = 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 cycleEditorType = currentState => {
const nextEditorType = (currentState.editorType + 1) % EDITORTYPES.length;
return {
...currentState,
editorType: nextEditorType,
};
};
const handleKeyUpEvent = e => {
if ((e.ctrlKey || event.metaKey) && e.which === 32) {
updateState(cycleEditorType);
}
};
const handleEditorExec = () => {
updateState(cycleEditorType);
};
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 getAceEditor = curmode => {
return (
<AceEditor
key="ace_text_editor"
{...allProps}
mode={curmode}
theme="chrome"
name="texttoggler"
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_text_editor"
{...allProps}
placeholder={placeholderProp}
value={data}
onChange={handleInputChangeAndPropagate}
onKeyUp={handleKeyUpEvent}
className={allProps.className + ' ' + styles.normalInput}
/>
);
};
const editor =
editorType === NORMALKEY
? getNormalEditor()
: getAceEditor(EDITORTYPES[editorType]);
return (
<span className="text_input_editor">
<label>{editor}</label>
<span
onClick={() => updateState(cycleEditorType)}
title={
'Change to ' +
EDITORTYPENAMES[(editorType + 1) % EDITORTYPES.length] +
' (Ctrl + Space)'
}
>
<span className={styles.modeType}>
{SHORTEDITORTYPENAMES[editorType]}
</span>
<i
key="icon_text_editor"
className={
'fa ' +
styles.modeToggleButton +
(editorType === NORMALKEY ? ' fa-expand' : ' fa-chevron-right')
}
/>
</span>
</span>
);
};
export default TextInput;

View File

@ -5,6 +5,7 @@ import { editItem, E_ONGOING_REQ } from './EditActions';
import globals from '../../../../Globals';
import { modalClose } from './EditActions';
import JsonInput from '../../../Common/CustomInputTypes/JsonInput';
import TextInput from '../../../Common/CustomInputTypes/TextInput';
import Button from '../../../Common/Button/Button';
import {
@ -19,6 +20,7 @@ import {
TIMETZ,
JSONB,
JSONDTYPE,
TEXT,
} from '../utils';
// import RichTextEditor from 'react-rte';
import { replace } from 'react-router-redux';
@ -84,180 +86,59 @@ class EditItem extends Component {
e.target.focus();
};
const standardEditProps = {
className: `form-control ${styles.insertBox}`,
onClick: clicker,
ref: inputRef,
'data-test': `typed-input-${i}`,
type: 'text',
defaultValue: oldItem[colName],
};
// Text type
let typedInput = (
<input
placeholder={getPlaceholder(colType)}
type="text"
className={'form-control ' + styles.insertBox}
onClick={clicker}
ref={inputRef}
defaultValue={oldItem[colName]}
data-test={`typed-input-${i}`}
/>
<input {...standardEditProps} placeholder={getPlaceholder(colType)} />
);
// Integer
if (colType === INTEGER) {
typedInput = (
<input
placeholder={getPlaceholder(colType)}
type="text"
className={'form-control ' + styles.insertBox}
onClick={clicker}
ref={inputRef}
defaultValue={oldItem[colName]}
data-test={`typed-input-${i}`}
/>
);
} else if (colType === BIGINT) {
typedInput = (
<input
placeholder={getPlaceholder(colType)}
type="text"
className={'form-control ' + styles.insertBox}
onClick={clicker}
ref={inputRef}
defaultValue={oldItem[colName]}
data-test={`typed-input-${i}`}
/>
);
} else if (colType === NUMERIC) {
typedInput = (
<input
placeholder={getPlaceholder(colType)}
type="text"
className={'form-control ' + styles.insertBox}
onClick={clicker}
ref={inputRef}
defaultValue={oldItem[colName]}
data-test={`typed-input-${i}`}
/>
);
} else if (colType === TIMESTAMP) {
typedInput = (
<input
placeholder={getPlaceholder(colType)}
type="text"
className={'form-control ' + styles.insertBox}
onClick={clicker}
ref={inputRef}
defaultValue={oldItem[colName]}
data-test={`typed-input-${i}`}
/>
);
} else if (colType === DATE) {
typedInput = (
<input
placeholder={getPlaceholder(colType)}
type="text"
className={'form-control ' + styles.insertBox}
onClick={clicker}
ref={inputRef}
defaultValue={oldItem[colName]}
data-test={`typed-input-${i}`}
/>
);
} else if (colType === TIMETZ) {
typedInput = (
<input
placeholder={getPlaceholder(colType)}
type="text"
className={'form-control ' + styles.insertBox}
onClick={clicker}
ref={inputRef}
defaultValue={oldItem[colName]}
data-test={`typed-input-${i}`}
/>
);
} else if (colType === JSONDTYPE || 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 = (
<JsonInput
standardProps={standardEditProps}
placeholderProp={getPlaceholder(colType)}
/>
);
} else if (colType === BOOLEAN) {
typedInput = (
<select
className={'form-control ' + styles.insertBox}
onClick={clicker}
ref={inputRef}
defaultValue={JSON.stringify(oldItem[colName])}
data-test={`typed-input-${i}`}
>
<option value="true">True</option>
<option value="false">False</option>
</select>
);
} else if (colType === UUID) {
typedInput = (
<input
placeholder={getPlaceholder(colType)}
type="text"
className={'form-control ' + styles.insertBox}
onClick={clicker}
ref={inputRef}
defaultValue={oldItem[colName]}
data-test={`typed-input-${i}`}
/>
);
} else {
// find value to be shown. rich text editor vs clone
let defaultValue = '';
let currentValue = '';
if (
this.state.editorColumnMap[colName] === null ||
this.state.editorColumnMap[colName] === undefined
) {
defaultValue = oldItem[colName];
} else if (this.state.editorColumnMap[colName] !== null) {
defaultValue = this.state.editorColumnMap[colName];
currentValue = this.state.editorColumnMap[colName];
}
if (currentValue !== '') {
switch (colType) {
case INTEGER:
case BIGINT:
case NUMERIC:
case TIMESTAMP:
case DATE:
case TIMETZ:
case UUID:
break;
case JSONB:
case JSONDTYPE:
typedInput = (
<span>
<input
placeholder={getPlaceholder(colType)}
type="text"
className={'form-control ' + styles.insertBox}
onClick={clicker}
ref={inputRef}
onChange={e => {
this.onTextChange(e, colName);
}}
value={currentValue}
data-test={`typed-input-${i}`}
/>
</span>
<JsonInput
standardProps={{
...standardEditProps,
defaultValue: JSON.stringify(oldItem[colName]),
}}
placeholderProp={getPlaceholder(colType)}
/>
);
} else {
break;
case TEXT:
typedInput = (
<span>
<input
placeholder={getPlaceholder(colType)}
type="text"
className={'form-control ' + styles.insertBox}
onClick={clicker}
ref={inputRef}
onChange={e => {
this.onTextChange(e, colName);
}}
value={defaultValue}
data-test={`typed-input-${i}`}
/>
</span>
<TextInput
standardProps={{ ...standardEditProps }}
placeholderProp={getPlaceholder(colType)}
/>
);
}
break;
case BOOLEAN:
typedInput = (
<select {...standardEditProps}>
<option value="true">True</option>
<option value="false">False</option>
</select>
);
break;
default:
break;
}
return (

View File

@ -5,8 +5,9 @@ import { insertItem, I_RESET } from './InsertActions';
import { ordinalColSort } from '../utils';
import { setTable } from '../DataActions';
import JsonInput from '../../../Common/CustomInputTypes/JsonInput';
import TextInput from '../../../Common/CustomInputTypes/TextInput';
import Button from '../../../Common/Button/Button';
import { getPlaceholder, BOOLEAN, JSONB, JSONDTYPE } from '../utils';
import { getPlaceholder, BOOLEAN, JSONB, JSONDTYPE, TEXT } from '../utils';
import { getParentNodeByClass } from '../../../../utils/domFunctions';
@ -28,8 +29,8 @@ class InsertItem extends Component {
nextInsert() {
// when use state object remember to do it inside a class method.
// Since the state variable lifecycle is tired to the instance of the class
// and making this change using an anonymous function will case errors.
// Since the state variable lifecycle is tied to the instance of the class
// and making this change using an anonymous function will cause errors.
this.setState({
insertedRows: this.state.insertedRows + 1,
});
@ -59,6 +60,13 @@ class InsertItem extends Component {
throw new NotFoundError();
}
const isColumnAutoIncrement = column => {
return (
column.column_default ===
"nextval('" + tableName + '_' + column.column_name + "_seq'::regclass)"
);
};
const _columns = schemas.find(
x => x.table_name === tableName && x.table_schema === currentSchema
).columns;
@ -81,14 +89,8 @@ class InsertItem extends Component {
}
e.target.focus();
};
const colDefault = col.column_default;
let isAutoIncrement = false;
if (
colDefault ===
"nextval('" + tableName + '_' + colName + "_seq'::regclass)"
) {
isAutoIncrement = true;
}
const isAutoIncrement = isColumnAutoIncrement(col);
const standardInputProps = {
className: `form-control ${styles.insertBox}`,
@ -144,34 +146,44 @@ class InsertItem extends Component {
);
}
if (colType === JSONDTYPE || colType === JSONB) {
// JSON/JSONB
typedInput = (
<JsonInput
standardProps={standardInputProps}
placeholderProp={getPlaceholder(colType)}
/>
);
}
if (colType === BOOLEAN) {
// Boolean
typedInput = (
<select
{...standardInputProps}
onClick={e => {
e.target.parentNode.parentNode.click();
e.target.focus();
}}
defaultValue={placeHolder}
>
<option value="" disabled>
-- bool --
</option>
<option value="true">True</option>
<option value="false">False</option>
</select>
);
switch (colType) {
case JSONB:
case JSONDTYPE:
typedInput = (
<JsonInput
standardProps={standardInputProps}
placeholderProp={getPlaceholder(colType)}
/>
);
break;
case TEXT:
typedInput = (
<TextInput
standardProps={standardInputProps}
placeholderProp={getPlaceholder(colType)}
/>
);
break;
case BOOLEAN:
typedInput = (
<select
{...standardInputProps}
onClick={e => {
e.target.parentNode.parentNode.click();
e.target.focus();
}}
defaultValue={placeHolder}
>
<option value="" disabled>
-- bool --
</option>
<option value="true">True</option>
<option value="false">False</option>
</select>
);
break;
default:
break;
}
return (