Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
514c2cc
chore: remove babel-polyfill
jmfrancois Mar 24, 2021
86a80df
chore(cmf): upgrade sentry
jmfrancois Mar 24, 2021
a5d851f
chore(ci): prettier
Mar 24, 2021
58ab0b1
feat: expose Sentry because it is bundled
jmfrancois Mar 24, 2021
5fa86c5
Merge branch 'jmfrancois/chore/cmf-upgrade-sentry' of github.com:Tale…
jmfrancois Mar 24, 2021
d5c9fd3
feat(forms): lazy load react-ace
jmfrancois Mar 30, 2021
66731fc
Revert "chore(cmf): upgrade sentry"
jmfrancois Mar 30, 2021
db51f8e
chore: revert
jmfrancois Mar 30, 2021
fe39d70
chore: cleanup
jmfrancois Mar 30, 2021
9e2c922
chore(ci): update code style outputs
Mar 30, 2021
6790ba4
chore(ci): prettier
Mar 30, 2021
f8a1e0d
chore: refactor
jmfrancois Mar 30, 2021
203352d
Merge branch 'jmfrancois/feat/forms-lazyload-react-ace' of github.com…
jmfrancois Mar 30, 2021
6f37c97
chore(ci): update code style outputs
Mar 30, 2021
d80854b
chore(ci): prettier
Mar 30, 2021
ddb4426
Merge branch 'master' into jmfrancois/feat/forms-lazyload-react-ace
jmfrancois May 24, 2021
1b682f2
chore: use skeleton
jmfrancois May 24, 2021
0858cc6
chore: remove try/catch block
jmfrancois May 24, 2021
54fb6ae
chore: refactor to make it a fn
jmfrancois May 24, 2021
ea6e81e
chore(ci): update code style outputs
May 24, 2021
f26f8f5
chore(ci): prettier
May 24, 2021
55c1c41
chore: refactor to not use suspense
jmfrancois May 25, 2021
f6106b2
Merge branch 'jmfrancois/feat/forms-lazyload-react-ace' of github.com…
jmfrancois May 25, 2021
30885bd
chore(ci): update code style outputs
May 25, 2021
04bdd05
chore(ci): prettier
May 25, 2021
235b584
Merge branch 'master' into jmfrancois/feat/forms-lazyload-react-ace
jmfrancois Aug 27, 2021
88c32c7
chore: refactor to use ui-script Talend obj
jmfrancois Aug 30, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions output/forms.eslint.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

/home/travis/build/Talend/ui/packages/forms/src/UIForm/fields/Code/Code.component.js
47:20 error 'value' is already declared in the upper scope no-shadow

✖ 1 problem (1 error, 0 warnings)
55 changes: 55 additions & 0 deletions packages/components/src/ImportLazy/ImportLazy.component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react';
import PropTypes from 'prop-types';
import Skeleton from '../Skeleton';

function DefaultSkeleton() {
return (
<div>
<Skeleton type={Skeleton.TYPES.text} size={Skeleton.SIZES.small} />
<Skeleton type={Skeleton.TYPES.text} size={Skeleton.SIZES.large} />
<Skeleton type={Skeleton.TYPES.text} size={Skeleton.SIZES.large} />
<Skeleton type={Skeleton.TYPES.text} size={Skeleton.SIZES.large} />
<Skeleton type={Skeleton.TYPES.button} />
</div>
);
}

/**
* ImportLazy component replace the need for react lazy
*/
export function ImportLazy({skeleton, name, version, path, varName, children}) {
const [added, setAdded] = React.useState(false);
const [loaded, setLoaded] = React.useState(false);
React.useEffect(() => {
const src = window.Talend.getAssetUrl({name, version, path});
const onload = () => {
if (!varName) {
setLoaded(true);
}
};
window.Talend.addScript({src, onload});
if (varName) {
const intervalId = setInterval(() => {
if (window[varName]) {
clearInterval(intervalId);
setLoaded(true);
}
}, 200);
}
}, []);
if (added && loaded) {
if (typeof children[0] === 'function') {
return children[0](loaded);
}
return children;
}
if (skeleton) {
return skeleton;
}
return <DefaultSkeleton />;
}

ImportLazy.propTypes = {
children: PropTypes.node,
skeleton: PropTypes.node,
};
1 change: 1 addition & 0 deletions packages/components/src/ImportLazy/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ImportLazy } from './ImportLazy.component';
2 changes: 2 additions & 0 deletions packages/components/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import CollapsiblePanel from './CollapsiblePanel';
import ConfirmDialog from './ConfirmDialog';
import Datalist from './Datalist';
import { ModelViewer, RecordsViewer } from './DataViewer';
import { ImportLazy } from './ImportLazy';
import {
DatePicker,
InputDatePicker,
Expand Down Expand Up @@ -165,4 +166,5 @@ export {
VirtualizedList,
WithDrawer,
getTheme,
ImportLazy,
};
240 changes: 106 additions & 134 deletions packages/forms/src/UIForm/fields/Code/Code.component.js
Original file line number Diff line number Diff line change
@@ -1,161 +1,143 @@
import PropTypes from 'prop-types';
import React from 'react';
import React, { useEffect } from 'react';
import { withTranslation } from 'react-i18next';
import keyCode from 'keycode';
import { ImportLazy, Skeleton } from '@talend/react-components';
import FieldTemplate from '../FieldTemplate';
import TextArea from '../TextArea';

import { generateId, generateDescriptionId, generateErrorId } from '../../Message/generateId';
import getDefaultT from '../../../translate';
import { I18N_DOMAIN_FORMS } from '../../../constants';

let CodeWidget = TextArea;
let AceEditor;
function CodeSkeleton() {
return (
<div>
<Skeleton type={Skeleton.TYPES.text} size={Skeleton.SIZES.small} />
<Skeleton type={Skeleton.TYPES.text} size={Skeleton.SIZES.large} />
<Skeleton type={Skeleton.TYPES.text} size={Skeleton.SIZES.large} />
<Skeleton type={Skeleton.TYPES.text} size={Skeleton.SIZES.large} />
<Skeleton type={Skeleton.TYPES.button} />
</div>
);
}

const DEFAULT_SET_OPTIONS = {
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
enableSnippets: true,
};

class WrappedTextArea extends React.PureComponent {
constructor() {
super();
// eslint-disable-next-line no-console
console.warn('CodeWidget react-ace not found, fallback to Textarea');
}

render() {
return <TextArea {...this.props} />;
}
}

// eslint-disable-next-line react/no-multi-comp
class Code extends React.Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
this.onFinish = this.onFinish.bind(this);
this.onLoad = this.onLoad.bind(this);
this.onBlur = this.onBlur.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);

// this hold the last time the ESC is pressed to offer an escape solution from keyboard trap
this.lastEsc = null;
}

componentDidUpdate() {
this.attachTextareaAttributes();
}
function Code(props) {
const { id, isValid, errorMessage, schema, value, valueIsUpdating, t } = props;
const { autoFocus, description, options, readOnly = false, title, labelProps } = schema;
const descriptionId = generateDescriptionId(id);
const errorId = generateErrorId(id);
const instructionsId = generateId(id, 'instructions');
const containerRef = React.useRef(null);
const [editor, setEditor] = React.useState(null);

useEffect(() => {
if (editor) {
const textarea = editor.textInput.getElement();
textarea.setAttribute('id', id);
textarea.setAttribute('aria-describedby', `${instructionsId} ${descriptionId} ${errorId}`);
}
}, [editor, instructionsId, descriptionId, errorId, id]);

onChange(value, event) {
this.props.onChange(event, { schema: this.props.schema, value });
function onChange(value, event) {
props.onChange(event, { schema: props.schema, value });
}

onFinish(event) {
this.props.onFinish(event, { schema: this.props.schema });
function onFinish(event) {
props.onFinish(event, { schema: props.schema });
}

onKeyDown(event) {
function onKeyDown(event) {
if (event.keyCode === keyCode.codes.esc) {
const now = Date.now();
if (this.lastEsc && this.lastEsc - now < 1000) {
this.lastEsc = null;
this.ref.focus();
this.editor.textInput.getElement().setAttribute('tabindex', -1);

if (containerRef.current.lastEsc && containerRef.current.lastEsc - now < 1000) {
containerRef.current.lastEsc = null;
containerRef.current.focus();
editor.textInput.getElement().setAttribute('tabindex', -1);
} else {
this.lastEsc = now;
containerRef.current.lastEsc = now;
}
} else {
this.lastEsc = null;
containerRef.current.lastEsc = null;
}
}

onBlur() {
this.editor.textInput.getElement().removeAttribute('tabindex');
}

onLoad(editor) {
this.editor = editor;
this.attachTextareaAttributes();
function onBlur() {
editor.textInput.getElement().removeAttribute('tabindex');
}

attachTextareaAttributes() {
if (this.editor) {
const textarea = this.editor.textInput.getElement();
textarea.setAttribute('id', this.props.id);
textarea.setAttribute(
'aria-describedby',
`${this.ids.instructionsId} ${this.ids.descriptionId} ${this.ids.errorId}`,
);
}
function onLoad(ace) {
setEditor(ace);
}

render() {
const { id, isValid, errorMessage, schema, value, valueIsUpdating, t } = this.props;
const { autoFocus, description, options, readOnly = false, title, labelProps } = schema;
const descriptionId = generateDescriptionId(id);
const errorId = generateErrorId(id);
const instructionsId = generateId(id, 'instructions');
this.ids = {
descriptionId,
errorId,
instructionsId,
};

return (
<FieldTemplate
description={description}
descriptionId={descriptionId}
errorId={errorId}
errorMessage={errorMessage}
id={id}
isValid={isValid}
label={title}
labelProps={labelProps}
required={schema.required}
valueIsUpdating={valueIsUpdating}
return (
<FieldTemplate
description={description}
descriptionId={descriptionId}
errorId={errorId}
errorMessage={errorMessage}
id={id}
isValid={isValid}
label={title}
labelProps={labelProps}
required={schema.required}
valueIsUpdating={valueIsUpdating}
>
<div // eslint-disable-line jsx-a11y/no-static-element-interactions
id={id && `${id}-editor-container`}
onBlur={onBlur}
onKeyDown={onKeyDown}
ref={containerRef}
tabIndex="-1"
>
<div // eslint-disable-line jsx-a11y/no-static-element-interactions
id={id && `${id}-editor-container`}
onBlur={this.onBlur}
onKeyDown={this.onKeyDown}
ref={ref => {
this.ref = ref;
}}
tabIndex="-1"
>
<div id={instructionsId} className="sr-only">
{t('TF_CODE_ESCAPE', {
defaultValue: 'To focus out of the editor, press ESC key twice.',
})}
</div>
<AceEditor
key="ace"
className="tf-widget-code form-control"
editorProps={{ $blockScrolling: Infinity }} // https://github.com/securingsincity/react-ace/issues/29
focus={autoFocus}
name={`${id}_wrapper`}
mode={options && options.language}
onBlur={this.onFinish}
onLoad={this.onLoad}
onChange={this.onChange}
// disabled is not supported by ace use readonly
// https://github.com/ajaxorg/ace/issues/406
readOnly={readOnly || schema.disabled || valueIsUpdating}
setOptions={DEFAULT_SET_OPTIONS}
showGutter={false}
showPrintMargin={false}
theme="chrome"
value={value}
width="auto"
{...options}
/>
<div id={instructionsId} className="sr-only">
{t('TF_CODE_ESCAPE', {
defaultValue: 'To focus out of the editor, press ESC key twice.',
})}
</div>
</FieldTemplate>
);
}
<ImportLazy
skeleton={<CodeSkeleton />}
name="react-ace"
version="6.2.0"
varName="ReactAce"
path="/dist/react-ace.min.js"
>
{AceEditor => (
<AceEditor
key="ace"
className="tf-widget-code form-control"
editorProps={{ $blockScrolling: Infinity }} // https://github.com/securingsincity/react-ace/issues/29
focus={autoFocus}
name={`${id}_wrapper`}
mode={options && options.language}
onBlur={onFinish}
onLoad={onLoad}
onChange={onChange}
// disabled is not supported by ace use readonly
// https://github.com/ajaxorg/ace/issues/406
readOnly={readOnly || schema.disabled || valueIsUpdating}
setOptions={DEFAULT_SET_OPTIONS}
showGutter={false}
showPrintMargin={false}
theme="chrome"
value={value}
width="auto"
{...options}
/>
)}
</ImportLazy>
</div>
</FieldTemplate>
);
}

if (process.env.NODE_ENV !== 'production') {
WrappedTextArea.propTypes = TextArea.propTypes;
Code.propTypes = {
id: PropTypes.string,
isValid: PropTypes.bool,
Expand Down Expand Up @@ -185,14 +167,4 @@ Code.defaultProps = {
t: getDefaultT(),
};

try {
/* eslint-disable global-require, import/no-extraneous-dependencies */
AceEditor = require('react-ace').default;
require('brace/ext/language_tools'); // https://github.com/securingsincity/react-ace/issues/95
/* eslint-enable global-require, import/no-extraneous-dependencies */
CodeWidget = Code;
} catch (error) {
CodeWidget = WrappedTextArea;
}

export default withTranslation(I18N_DOMAIN_FORMS)(CodeWidget);
export default withTranslation(I18N_DOMAIN_FORMS)(Code);
Loading