Feature/editor cursor jumping (#485)
* To avoid the cursor jumps in the Monoco editor, never use "value=xxx" in conjuntion with "onChange". Use defaultValue instead, and make sure that the editor is rerendered when that value is changed. * linting
This commit is contained in:
parent
b432f22fe0
commit
e95b3b03f3
|
@ -1,4 +1,4 @@
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState, useRef } from 'react';
|
||||||
import Editor from '@monaco-editor/react';
|
import Editor from '@monaco-editor/react';
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
import merge from 'lodash/merge';
|
import merge from 'lodash/merge';
|
||||||
|
@ -37,6 +37,7 @@ export default function ReactFormBuilder({
|
||||||
const DATA_EXTENSION = '-exampledata.json';
|
const DATA_EXTENSION = '-exampledata.json';
|
||||||
|
|
||||||
const [fetchFailed, setFetchFailed] = useState<boolean>(false);
|
const [fetchFailed, setFetchFailed] = useState<boolean>(false);
|
||||||
|
const [ready, setReady] = useState<boolean>(false);
|
||||||
|
|
||||||
const [strSchema, setStrSchema] = useState<string>('');
|
const [strSchema, setStrSchema] = useState<string>('');
|
||||||
const [debouncedStrSchema] = useDebounce(strSchema, 500);
|
const [debouncedStrSchema] = useDebounce(strSchema, 500);
|
||||||
|
@ -55,6 +56,24 @@ export default function ReactFormBuilder({
|
||||||
const [baseFileName, setBaseFileName] = useState<string>('');
|
const [baseFileName, setBaseFileName] = useState<string>('');
|
||||||
const [newFileName, setNewFileName] = useState<string>('');
|
const [newFileName, setNewFileName] = useState<string>('');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This section gives us direct pointers to the monoco editors so that
|
||||||
|
* we can update their values. Using state variables directly on the monoco editor
|
||||||
|
* causes the cursor to jump to the bottom if two letters are pressed simultaneously.
|
||||||
|
*/
|
||||||
|
const schemaEditorRef = useRef(null);
|
||||||
|
const uiEditorRef = useRef(null);
|
||||||
|
const dataEditorRef = useRef(null);
|
||||||
|
function handleSchemaEditorDidMount(editor: any) {
|
||||||
|
schemaEditorRef.current = editor;
|
||||||
|
}
|
||||||
|
function handleUiEditorDidMount(editor: any) {
|
||||||
|
uiEditorRef.current = editor;
|
||||||
|
}
|
||||||
|
function handleDataEditorDidMount(editor: any) {
|
||||||
|
dataEditorRef.current = editor;
|
||||||
|
}
|
||||||
|
|
||||||
const saveFile = useCallback(
|
const saveFile = useCallback(
|
||||||
(file: File, create: boolean = false) => {
|
(file: File, create: boolean = false) => {
|
||||||
let httpMethod = 'PUT';
|
let httpMethod = 'PUT';
|
||||||
|
@ -88,7 +107,16 @@ export default function ReactFormBuilder({
|
||||||
};
|
};
|
||||||
|
|
||||||
const isReady = () => {
|
const isReady = () => {
|
||||||
return strSchema !== '' && strUI !== '' && strFormData !== '';
|
// Use a ready flag so that we still allow people to completely delete
|
||||||
|
// the schema, ui or data if they want to clear it out.
|
||||||
|
if (ready) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (strSchema !== '' && strUI !== '' && strFormData !== '') {
|
||||||
|
setReady(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Auto save schema changes
|
// Auto save schema changes
|
||||||
|
@ -114,7 +142,7 @@ export default function ReactFormBuilder({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
/**
|
/**
|
||||||
* we need to run the schema and ui through a backend call before rendering the form
|
* we need to run the schema and ui through a backend call before rendering the form,
|
||||||
* so it can handle certain server side changes, such as jinja rendering and populating dropdowns, etc.
|
* so it can handle certain server side changes, such as jinja rendering and populating dropdowns, etc.
|
||||||
*/
|
*/
|
||||||
const url: string = '/tasks/prepare-form';
|
const url: string = '/tasks/prepare-form';
|
||||||
|
@ -173,6 +201,51 @@ export default function ReactFormBuilder({
|
||||||
setSelectedIndex(evt.selectedIndex);
|
setSelectedIndex(evt.selectedIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateStrSchema = (value: string) => {
|
||||||
|
if (schemaEditorRef && schemaEditorRef.current) {
|
||||||
|
// @ts-ignore
|
||||||
|
schemaEditorRef.current.setValue(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateStrUi = (value: string) => {
|
||||||
|
if (uiEditorRef && uiEditorRef.current) {
|
||||||
|
// @ts-ignore
|
||||||
|
uiEditorRef.current.setValue(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateStrData = (value: string) => {
|
||||||
|
// Only update the data if it is different from what is already there, this prevents
|
||||||
|
// cursor from jumping to the top each time you type a letter.
|
||||||
|
if (
|
||||||
|
dataEditorRef &&
|
||||||
|
dataEditorRef.current &&
|
||||||
|
// @ts-ignore
|
||||||
|
value !== dataEditorRef.current.getValue()
|
||||||
|
) {
|
||||||
|
// @ts-ignore
|
||||||
|
dataEditorRef.current.setValue(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function updateData(newData: object) {
|
||||||
|
setFormData(newData);
|
||||||
|
const newDataStr = JSON.stringify(newData, null, 2);
|
||||||
|
if (newDataStr !== strFormData) {
|
||||||
|
updateStrData(newDataStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDataFromStr(newDataStr: string) {
|
||||||
|
try {
|
||||||
|
const newData = JSON.parse(newDataStr);
|
||||||
|
setFormData(newData);
|
||||||
|
} catch (e) {
|
||||||
|
/* empty */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setJsonSchemaFromResponseJson(result: any) {
|
function setJsonSchemaFromResponseJson(result: any) {
|
||||||
setStrSchema(result.file_contents);
|
setStrSchema(result.file_contents);
|
||||||
}
|
}
|
||||||
|
@ -232,29 +305,13 @@ export default function ReactFormBuilder({
|
||||||
|
|
||||||
function insertFields(schema: any, ui: any, data: any) {
|
function insertFields(schema: any, ui: any, data: any) {
|
||||||
setFormData(merge(formData, data));
|
setFormData(merge(formData, data));
|
||||||
setStrFormData(JSON.stringify(formData, null, 2));
|
updateStrData(JSON.stringify(formData, null, 2));
|
||||||
|
|
||||||
const tempSchema = merge(JSON.parse(strSchema), schema);
|
const tempSchema = merge(JSON.parse(strSchema), schema);
|
||||||
setStrSchema(JSON.stringify(tempSchema, null, 2));
|
updateStrSchema(JSON.stringify(tempSchema, null, 2));
|
||||||
|
|
||||||
const tempUI = merge(JSON.parse(strUI), ui);
|
const tempUI = merge(JSON.parse(strUI), ui);
|
||||||
setStrUI(JSON.stringify(tempUI, null, 2));
|
updateStrUi(JSON.stringify(tempUI, null, 2));
|
||||||
}
|
|
||||||
|
|
||||||
function updateData(newData: object) {
|
|
||||||
setFormData(newData);
|
|
||||||
const newDataStr = JSON.stringify(newData, null, 2);
|
|
||||||
if (newDataStr !== strFormData) {
|
|
||||||
setStrFormData(newDataStr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function updateDataFromStr(newDataStr: string) {
|
|
||||||
try {
|
|
||||||
const newData = JSON.parse(newDataStr);
|
|
||||||
setFormData(newData);
|
|
||||||
} catch (e) {
|
|
||||||
/* empty */
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isReady()) {
|
if (!isReady()) {
|
||||||
|
@ -340,8 +397,9 @@ export default function ReactFormBuilder({
|
||||||
height={600}
|
height={600}
|
||||||
width="auto"
|
width="auto"
|
||||||
defaultLanguage="json"
|
defaultLanguage="json"
|
||||||
value={strSchema}
|
defaultValue={strSchema}
|
||||||
onChange={(value) => setStrSchema(value || '')}
|
onChange={(value) => setStrSchema(value || '')}
|
||||||
|
onMount={handleSchemaEditorDidMount}
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
|
@ -359,8 +417,9 @@ export default function ReactFormBuilder({
|
||||||
height={600}
|
height={600}
|
||||||
width="auto"
|
width="auto"
|
||||||
defaultLanguage="json"
|
defaultLanguage="json"
|
||||||
value={strUI}
|
defaultValue={strUI}
|
||||||
onChange={(value) => setStrUI(value || '')}
|
onChange={(value) => setStrUI(value || '')}
|
||||||
|
onMount={handleUiEditorDidMount}
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
|
@ -372,8 +431,9 @@ export default function ReactFormBuilder({
|
||||||
height={600}
|
height={600}
|
||||||
width="auto"
|
width="auto"
|
||||||
defaultLanguage="json"
|
defaultLanguage="json"
|
||||||
value={strFormData}
|
defaultValue={strFormData}
|
||||||
onChange={(value: any) => updateDataFromStr(value || '')}
|
onChange={(value: any) => updateDataFromStr(value || '')}
|
||||||
|
onMount={handleDataEditorDidMount}
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
|
|
|
@ -432,7 +432,7 @@ export default function ProcessModelEditDiagram() {
|
||||||
const unitTestModdleElements = extensionElements
|
const unitTestModdleElements = extensionElements
|
||||||
.get('values')
|
.get('values')
|
||||||
.filter(function getInstanceOfType(e: any) {
|
.filter(function getInstanceOfType(e: any) {
|
||||||
return e.$instanceOf('spiffworkflow:unitTests');
|
return e.$instanceOf('spiffworkflow:UnitTests');
|
||||||
})[0];
|
})[0];
|
||||||
if (unitTestModdleElements) {
|
if (unitTestModdleElements) {
|
||||||
return unitTestModdleElements.unitTests;
|
return unitTestModdleElements.unitTests;
|
||||||
|
@ -782,7 +782,7 @@ export default function ProcessModelEditDiagram() {
|
||||||
width="auto"
|
width="auto"
|
||||||
defaultLanguage="json"
|
defaultLanguage="json"
|
||||||
options={Object.assign(jsonEditorOptions(), {})}
|
options={Object.assign(jsonEditorOptions(), {})}
|
||||||
value={inputJson}
|
defaultValue={inputJson}
|
||||||
onChange={handleEditorScriptTestUnitInputChange}
|
onChange={handleEditorScriptTestUnitInputChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -795,7 +795,7 @@ export default function ProcessModelEditDiagram() {
|
||||||
width="auto"
|
width="auto"
|
||||||
defaultLanguage="json"
|
defaultLanguage="json"
|
||||||
options={Object.assign(jsonEditorOptions(), {})}
|
options={Object.assign(jsonEditorOptions(), {})}
|
||||||
value={outputJson}
|
defaultValue={outputJson}
|
||||||
onChange={handleEditorScriptTestUnitOutputChange}
|
onChange={handleEditorScriptTestUnitOutputChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -807,19 +807,25 @@ export default function ProcessModelEditDiagram() {
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
const scriptEditor = () => {
|
const scriptEditor = () => {
|
||||||
|
if (!showScriptEditor) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Editor
|
<Editor
|
||||||
height={500}
|
height={500}
|
||||||
width="auto"
|
width="auto"
|
||||||
options={generalEditorOptions()}
|
options={generalEditorOptions()}
|
||||||
defaultLanguage="python"
|
defaultLanguage="python"
|
||||||
value={scriptText}
|
defaultValue={scriptText}
|
||||||
onChange={handleEditorScriptChange}
|
onChange={handleEditorScriptChange}
|
||||||
onMount={handleEditorDidMount}
|
onMount={handleEditorDidMount}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
const scriptEditorAndTests = () => {
|
const scriptEditorAndTests = () => {
|
||||||
|
if (!showScriptEditor) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
let scriptName = '';
|
let scriptName = '';
|
||||||
if (scriptElement) {
|
if (scriptElement) {
|
||||||
scriptName = (scriptElement as any).di.bpmnElement.name;
|
scriptName = (scriptElement as any).di.bpmnElement.name;
|
||||||
|
|
Loading…
Reference in New Issue