From 7a683324f25c6b0679083e54dc8123f30f6bb2d0 Mon Sep 17 00:00:00 2001 From: Anne Ogborn <47622029+AnnieAtHasura@users.noreply.github.com> Date: Mon, 22 Jul 2019 15:17:31 +0530 Subject: [PATCH] add multiline and rich editing to text fields (close #458) (#2498) --- .../{JsonInput.scss => CustomInput.scss} | 12 +- .../Common/CustomInputTypes/JsonInput.js | 11 +- .../Common/CustomInputTypes/TextInput.js | 166 ++++++++++++++ .../Services/Data/TableBrowseRows/EditItem.js | 213 ++++-------------- .../Data/TableInsertItem/InsertItem.js | 90 ++++---- 5 files changed, 281 insertions(+), 211 deletions(-) rename console/src/components/Common/CustomInputTypes/{JsonInput.scss => CustomInput.scss} (51%) create mode 100644 console/src/components/Common/CustomInputTypes/TextInput.js diff --git a/console/src/components/Common/CustomInputTypes/JsonInput.scss b/console/src/components/Common/CustomInputTypes/CustomInput.scss similarity index 51% rename from console/src/components/Common/CustomInputTypes/JsonInput.scss rename to console/src/components/Common/CustomInputTypes/CustomInput.scss index ae10091148f1e..15f125f870106 100644 --- a/console/src/components/Common/CustomInputTypes/JsonInput.scss +++ b/console/src/components/Common/CustomInputTypes/CustomInput.scss @@ -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; } \ No newline at end of file diff --git a/console/src/components/Common/CustomInputTypes/JsonInput.js b/console/src/components/Common/CustomInputTypes/JsonInput.js index 6d10bf8d9e001..4ba824abab721 100644 --- a/console/src/components/Common/CustomInputTypes/JsonInput.js +++ b/console/src/components/Common/CustomInputTypes/JsonInput.js @@ -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)' } /> diff --git a/console/src/components/Common/CustomInputTypes/TextInput.js b/console/src/components/Common/CustomInputTypes/TextInput.js new file mode 100644 index 0000000000000..98c2f74faf22a --- /dev/null +++ b/console/src/components/Common/CustomInputTypes/TextInput.js @@ -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 ( + + ); + }; + + const getNormalEditor = () => { + return ( + + ); + }; + + const editor = + editorType === NORMALKEY + ? getNormalEditor() + : getAceEditor(EDITORTYPES[editorType]); + + return ( + + + updateState(cycleEditorType)} + title={ + 'Change to ' + + EDITORTYPENAMES[(editorType + 1) % EDITORTYPES.length] + + ' (Ctrl + Space)' + } + > + + {SHORTEDITORTYPENAMES[editorType]} + + + + + ); +}; +export default TextInput; diff --git a/console/src/components/Services/Data/TableBrowseRows/EditItem.js b/console/src/components/Services/Data/TableBrowseRows/EditItem.js index 84b2b78e2d619..1fc88f16c09e4 100644 --- a/console/src/components/Services/Data/TableBrowseRows/EditItem.js +++ b/console/src/components/Services/Data/TableBrowseRows/EditItem.js @@ -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 = ( - + ); - // Integer - if (colType === INTEGER) { - typedInput = ( - - ); - } else if (colType === BIGINT) { - typedInput = ( - - ); - } else if (colType === NUMERIC) { - typedInput = ( - - ); - } else if (colType === TIMESTAMP) { - typedInput = ( - - ); - } else if (colType === DATE) { - typedInput = ( - - ); - } else if (colType === TIMETZ) { - typedInput = ( - - ); - } 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 = ( - - ); - } else if (colType === BOOLEAN) { - typedInput = ( - - ); - } else if (colType === UUID) { - typedInput = ( - - ); - } 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 = ( - - { - this.onTextChange(e, colName); - }} - value={currentValue} - data-test={`typed-input-${i}`} - /> - + ); - } else { + break; + case TEXT: typedInput = ( - - { - this.onTextChange(e, colName); - }} - value={defaultValue} - data-test={`typed-input-${i}`} - /> - + + ); + break; + case BOOLEAN: + typedInput = ( + ); - } + break; + default: + break; } return ( diff --git a/console/src/components/Services/Data/TableInsertItem/InsertItem.js b/console/src/components/Services/Data/TableInsertItem/InsertItem.js index b0dc6aaa5bf57..d86465b5ba5da 100644 --- a/console/src/components/Services/Data/TableInsertItem/InsertItem.js +++ b/console/src/components/Services/Data/TableInsertItem/InsertItem.js @@ -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 = ( - - ); - } - - if (colType === BOOLEAN) { - // Boolean - typedInput = ( - - ); + switch (colType) { + case JSONB: + case JSONDTYPE: + typedInput = ( + + ); + break; + case TEXT: + typedInput = ( + + ); + break; + case BOOLEAN: + typedInput = ( + + ); + break; + default: + break; } return (